@bubo-squared/gyroview 0.4.1 → 0.5.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 (34) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +7 -7
  3. package/dist/packages/adapters/mediabunny/src/MediabunnyCodecReader.js +10 -4
  4. package/dist/packages/adapters/mediabunny/src/decoderConfigurations.js +20 -4
  5. package/dist/packages/adapters/three/src/ThreeFrameRenderer.js +32 -13
  6. package/dist/packages/adapters/three/src/drawSchedules.js +13 -0
  7. package/dist/packages/adapters/three/src/index.js +1 -0
  8. package/dist/packages/core/src/application/download/FileDownload.js +17 -6
  9. package/dist/packages/core/src/application/download/SampleCursor.js +4 -4
  10. package/dist/packages/core/src/application/download/Transfers.js +7 -0
  11. package/dist/packages/core/src/application/recording/motionOf.js +5 -1
  12. package/dist/packages/core/src/application/stabilization/StabilizingFrameSink.js +4 -2
  13. package/dist/packages/core/src/domain/download/DownloadPolicy.js +10 -1
  14. package/dist/packages/core/src/domain/format/mp4/hevcCodecString.js +161 -0
  15. package/dist/packages/core/src/domain/format/records/gyro/SampleColumns.js +9 -2
  16. package/dist/packages/core/src/domain/motion/gyro/GyroTrack.js +14 -4
  17. package/dist/packages/core/src/domain/motion/imu/ImuFrame.js +35 -3
  18. package/dist/packages/core/src/domain/motion/mounting/Mounting.js +117 -0
  19. package/dist/packages/core/src/domain/motion/orientation/OrientationTrack.js +6 -4
  20. package/dist/packages/core/src/domain/motion/orientation/estimateGyroBias.js +9 -1
  21. package/dist/packages/core/src/domain/motion/orientation/gravity.js +1 -1
  22. package/dist/packages/core/src/index.js +4 -1
  23. package/dist/packages/core/src/shared/binary/ByteReader.js +10 -1
  24. package/dist/packages/core/src/shared/index.js +1 -0
  25. package/dist/packages/core/src/shared/math/Matrix3.js +18 -1
  26. package/dist/packages/core/src/shared/math/Quaternion.js +1 -1
  27. package/dist/packages/core/src/shared/math/Vector3.js +5 -1
  28. package/dist/packages/core/src/shared/math/minimum.js +17 -0
  29. package/dist/packages/player/src/composition/AnimationFrameDraws.js +38 -0
  30. package/dist/packages/player/src/composition/animationFrames.js +9 -0
  31. package/dist/packages/player/src/composition/buildPipeline.js +13 -7
  32. package/dist/packages/player/src/player/FrameLoop.js +1 -6
  33. package/dist/standalone.js +58 -59
  34. package/package.json +1 -1
@@ -38,14 +38,24 @@ var GyroTrack = class {
38
38
  }
39
39
  sampleAt(index) {
40
40
  ensureIndexInRange(index, this.length, "gyro sample");
41
- const base = index * 3;
42
41
  return {
43
42
  captureTime: microseconds(this.captureTimeStore[index] ?? 0),
44
- acceleration: this.vectorAt(this.accelerationStore, base),
45
- angularVelocity: this.vectorAt(this.angularVelocityStore, base)
43
+ acceleration: this.vectorAt(this.accelerationStore, index),
44
+ angularVelocity: this.vectorAt(this.angularVelocityStore, index)
46
45
  };
47
46
  }
48
- vectorAt(store, base) {
47
+ /**
48
+ * A sample's angular velocity alone, for the loops over every sample that need nothing else.
49
+ */
50
+ angularVelocityAt(index) {
51
+ ensureIndexInRange(index, this.length, "gyro sample");
52
+ return this.vectorAt(this.angularVelocityStore, index);
53
+ }
54
+ /**
55
+ * The vector at an index already checked.
56
+ */
57
+ vectorAt(store, index) {
58
+ const base = index * 3;
49
59
  return [
50
60
  store[base] ?? NaN,
51
61
  store[base + 1] ?? NaN,
@@ -107,6 +107,34 @@ var X6_IMU_FRAME = measuredImuFrame("X6", QUARTER_TURN_ABOUT_LATERAL);
107
107
  */
108
108
  var X4_AIR_IMU_FRAME = measuredImuFrame("X4 Air", QUARTER_TURN_ABOUT_LATERAL);
109
109
  /**
110
+ * Measured on one Antigravity A1 recording (ADR 0009): the drone's lens axis is its vertical, the
111
+ * IMU's y, lens 0 facing up; the ranking puts this quarter turn about that axis first, the X5's
112
+ * arrangement fourth.
113
+ */
114
+ var A1_IMU_FRAME = measuredImuFrame("A1", [
115
+ "-z",
116
+ "-x",
117
+ "y"
118
+ ]);
119
+ /**
120
+ * Measured on one X3 recording (ADR 0009): the X5's arrangement again. The camera stood still on
121
+ * its side, so the levelling ranking decided it rather than the stillness ranking: it alone of
122
+ * the four that put gravity on the body's axis stands the picture as level as Studio's.
123
+ */
124
+ var X3_IMU_FRAME = measuredImuFrame("X3", QUARTER_TURN_ABOUT_LATERAL);
125
+ /**
126
+ * Assumed for the ONE RS until a recording that turns is ranked (ADR 0009). On one tripod
127
+ * recording of the 1-inch 360 edition, standing upright, gravity lies along the IMU's x, so the
128
+ * body's down is the IMU's x; of the four arrangements that agree, the levelling ranking puts
129
+ * this quarter turn about the lens axis first in both browsers, by too little to decide it. The
130
+ * model name covers the 360 lens module too, assumed alike.
131
+ */
132
+ var ONE_RS_IMU_FRAME = assumedImuFrame("ONE RS (unverified)", [
133
+ "-y",
134
+ "x",
135
+ "z"
136
+ ]);
137
+ /**
110
138
  * Until a recording proves otherwise, the IMU is assumed aligned with the body.
111
139
  */
112
140
  var ALIGNED_IMU_FRAME = assumedImuFrame("aligned (unverified)", [
@@ -116,12 +144,16 @@ var ALIGNED_IMU_FRAME = assumedImuFrame("aligned (unverified)", [
116
144
  ]);
117
145
  /**
118
146
  * Keyed by the whole model name: a name that extends a measured one ("X4 Air" of "X4", an "X5
119
- * Pro") is another camera, unmeasured until a recording of it is ranked.
147
+ * Pro") is another camera, unmeasured until a recording of it is ranked. Every frame is measured
148
+ * but the ONE RS's, assumed and reported as such.
120
149
  */
121
150
  var IMU_FRAMES_BY_MODEL = /* @__PURE__ */ new Map([
151
+ ["Antigravity A1", A1_IMU_FRAME],
152
+ ["Insta360 X3", X3_IMU_FRAME],
122
153
  ["Insta360 X4 Air", X4_AIR_IMU_FRAME],
123
154
  ["Insta360 X5", X5_IMU_FRAME],
124
- ["Insta360 X6", X6_IMU_FRAME]
155
+ ["Insta360 X6", X6_IMU_FRAME],
156
+ ["Insta360 OneRS", ONE_RS_IMU_FRAME]
125
157
  ]);
126
158
  /**
127
159
  * The IMU frame for a recording, chosen from the camera model the file names. A starting point
@@ -131,4 +163,4 @@ function imuFrameFor(hints) {
131
163
  return IMU_FRAMES_BY_MODEL.get(hints.model?.trim() ?? "") ?? ALIGNED_IMU_FRAME;
132
164
  }
133
165
  //#endregion
134
- export { ALIGNED_IMU_FRAME, X4_AIR_IMU_FRAME, X5_IMU_FRAME, X6_IMU_FRAME, assumedImuFrame, imuFrameFor, isProperRotation, toBodyFrame };
166
+ export { A1_IMU_FRAME, ALIGNED_IMU_FRAME, ONE_RS_IMU_FRAME, X3_IMU_FRAME, X4_AIR_IMU_FRAME, X5_IMU_FRAME, X6_IMU_FRAME, assumedImuFrame, imuFrameFor, isProperRotation, toBodyFrame };
@@ -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 };
@@ -1,5 +1,5 @@
1
1
  import { ensureInvariant } from "../../../shared/errors/GyroViewError.js";
2
- import { seconds } from "../../../shared/units/time.js";
2
+ import { microseconds, seconds } from "../../../shared/units/time.js";
3
3
  import { ZERO_VECTOR3, addVectors, isFiniteVector, scaleVector, subtractVectors } from "../../../shared/math/Vector3.js";
4
4
  import { clamp } from "../../../shared/math/clamp.js";
5
5
  import { toBodyFrame } from "../imu/ImuFrame.js";
@@ -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;
@@ -107,7 +109,7 @@ var OrientationTrack = class OrientationTrack {
107
109
  * lookup's binary search holds.
108
110
  */
109
111
  function sampleVideoTimes(gyro, clock) {
110
- return Float64Array.from({ length: gyro.length }, (_unused, index) => clock.gyroVideoTimeOf(gyro.sampleAt(index).captureTime));
112
+ return gyro.captureTimes.map((captureTime) => clock.gyroVideoTimeOf(microseconds(captureTime)));
111
113
  }
112
114
  function finiteSample(gyro, index) {
113
115
  const sample = gyro.sampleAt(index);
@@ -18,7 +18,7 @@ var FALLBACK_INTERVAL = secondsToMicroseconds(seconds(1));
18
18
  function stillestWindow(gyro, frame, length) {
19
19
  const interval = microsecondsToSeconds(gyro.meanSampleInterval ?? FALLBACK_INTERVAL);
20
20
  const size = Math.max(1, Math.min(gyro.length, Math.round(length / interval)));
21
- const magnitudes = Float64Array.from({ length: gyro.length }, (_unused, index) => magnitudeOf(toBodyFrame(frame, gyro.sampleAt(index).angularVelocity)));
21
+ const magnitudes = rateMagnitudesOf(gyro, frame);
22
22
  let windowSum = 0;
23
23
  for (let index = 0; index < size; index += 1) windowSum += magnitudes[index] ?? 0;
24
24
  let best = {
@@ -38,6 +38,14 @@ function stillestWindow(gyro, frame, length) {
38
38
  };
39
39
  }
40
40
  /**
41
+ * How fast the body turns at each sample, in radians per second.
42
+ */
43
+ function rateMagnitudesOf(gyro, frame) {
44
+ const magnitudes = new Float64Array(gyro.length);
45
+ for (let index = 0; index < gyro.length; index += 1) magnitudes[index] = magnitudeOf(toBodyFrame(frame, gyro.angularVelocityAt(index)));
46
+ return magnitudes;
47
+ }
48
+ /**
41
49
  * The mean of one vector quantity over a window of samples, in the body frame.
42
50
  */
43
51
  function meanOverWindow({ gyro, frame }, window, quantity) {
@@ -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 };
@@ -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,6 +28,7 @@ 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";
@@ -68,4 +71,4 @@ import { WallClock } from "./application/playback/WallClock.js";
68
71
  import { isFlowing } from "./domain/playback/PlayerState.js";
69
72
  import { TypedEmitter } from "./shared/events/TypedEmitter.js";
70
73
  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 };
74
+ 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, clampView, degrees, detectLensLayout, displayConversionsOf, downloadPolicyFor, ensureIndexInRange, ensureInvariant, fileNameOfUrl, hasErrorCode, hevcCodecStringOf, 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 };
@@ -10,6 +10,11 @@ var FLOAT64_SIZE = 8;
10
10
  var IS_LITTLE_ENDIAN = true;
11
11
  var IS_BIG_ENDIAN = false;
12
12
  /**
13
+ * The safe integer range as big integers, made once: a gyro record reads a stamp per sample.
14
+ */
15
+ var MIN_SAFE_BIGINT = BigInt(Number.MIN_SAFE_INTEGER);
16
+ var MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
17
+ /**
13
18
  * Bounds-checked reads over a byte array: little-endian for Insta360's records, big-endian for
14
19
  * ISO BMFF boxes. Every read is positional, so callers express the layouts they parse as named
15
20
  * offsets rather than as a moving cursor.
@@ -36,6 +41,10 @@ var ByteReader = class {
36
41
  this.ensureAvailable(offset, UINT32_SIZE);
37
42
  return this.view.getUint32(offset, IS_LITTLE_ENDIAN);
38
43
  }
44
+ uint16BeAt(offset) {
45
+ this.ensureAvailable(offset, UINT16_SIZE);
46
+ return this.view.getUint16(offset, IS_BIG_ENDIAN);
47
+ }
39
48
  uint32BeAt(offset) {
40
49
  this.ensureAvailable(offset, UINT32_SIZE);
41
50
  return this.view.getUint32(offset, IS_BIG_ENDIAN);
@@ -84,7 +93,7 @@ var ByteReader = class {
84
93
  return Array.from(this.bytesAt(offset, length), (byte) => String.fromCodePoint(byte)).join("");
85
94
  }
86
95
  toSafeNumber(value, offset) {
87
- if (!(value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER))) throw new GyroViewError("binary-unsafe-integer", `64-bit integer at offset ${offset} (${value.toString()}) exceeds the safe integer range`);
96
+ if (!(value >= MIN_SAFE_BIGINT && value <= MAX_SAFE_BIGINT)) throw new GyroViewError("binary-unsafe-integer", `64-bit integer at offset ${offset} (${value.toString()}) exceeds the safe integer range`);
88
97
  return Number(value);
89
98
  }
90
99
  ensureAvailable(offset, length) {
@@ -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";
@@ -73,6 +73,23 @@ function multiplyMatrices(a, b) {
73
73
  a20 * b02 + a21 * b12 + a22 * b22
74
74
  ];
75
75
  }
76
+ /**
77
+ * The transpose, which for a rotation is its inverse.
78
+ */
79
+ function transposeMatrix(m) {
80
+ const [m00, m01, m02, m10, m11, m12, m20, m21, m22] = m;
81
+ return [
82
+ m00,
83
+ m10,
84
+ m20,
85
+ m01,
86
+ m11,
87
+ m21,
88
+ m02,
89
+ m12,
90
+ m22
91
+ ];
92
+ }
76
93
  function transformVector(m, v) {
77
94
  const [m00, m01, m02, m10, m11, m12, m20, m21, m22] = m;
78
95
  const [x, y, z] = v;
@@ -90,4 +107,4 @@ function determinantOf(m) {
90
107
  return m00 * (m11 * m22 - m12 * m21) - m01 * (m10 * m22 - m12 * m20) + m02 * (m10 * m21 - m11 * m20);
91
108
  }
92
109
  //#endregion
93
- export { IDENTITY_MATRIX3, determinantOf, multiplyMatrices, rotationAboutX, rotationAboutY, rotationAboutZ, transformVector };
110
+ export { IDENTITY_MATRIX3, determinantOf, multiplyMatrices, rotationAboutX, rotationAboutY, rotationAboutZ, transformVector, transposeMatrix };
@@ -62,7 +62,7 @@ function conjugateQuaternion(q) {
62
62
  }
63
63
  function normalizeQuaternion(q) {
64
64
  const [x, y, z, w] = q;
65
- const length = Math.hypot(x, y, z, w);
65
+ const length = Math.sqrt(x * x + y * y + z * z + w * w);
66
66
  return length === 0 ? IDENTITY_QUATERNION : [
67
67
  x / length,
68
68
  y / length,
@@ -4,8 +4,12 @@ var ZERO_VECTOR3 = [
4
4
  0,
5
5
  0
6
6
  ];
7
+ /**
8
+ * The square root of the squares, not `Math.hypot`: several times slower in V8, it guards against
9
+ * overflow no reading or direction here comes near, and gyro integration takes a million of these.
10
+ */
7
11
  function magnitudeOf(vector) {
8
- return Math.hypot(vector[0], vector[1], vector[2]);
12
+ return Math.sqrt(dotProduct(vector, vector));
9
13
  }
10
14
  function dotProduct(a, b) {
11
15
  return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
@@ -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 };
@@ -0,0 +1,38 @@
1
+ import { ANIMATION_FRAMES } from "./animationFrames.js";
2
+ //#region ../../packages/player/src/composition/AnimationFrameDraws.ts
3
+ /**
4
+ * Draws a renderer's changed settings once at the next animation frame, however many changes
5
+ * come before it (ADR 0035): the screen shows one picture a frame, and WebKit hands the page
6
+ * every pointer move of a drag, thousands a second from a fast mouse, which drawn one by one
7
+ * leave no time for the frames of the recording. One schedule serves one renderer.
8
+ */
9
+ var AnimationFrameDraws = class {
10
+ frames;
11
+ pendingDraw;
12
+ handle;
13
+ constructor(frames = ANIMATION_FRAMES) {
14
+ this.frames = frames;
15
+ }
16
+ /**
17
+ * The latest draw asked for is the one drawn.
18
+ */
19
+ request(draw) {
20
+ const isFrameRequested = this.pendingDraw !== void 0;
21
+ this.pendingDraw = draw;
22
+ if (!isFrameRequested) this.handle = this.frames.request(this.drawPending);
23
+ }
24
+ cancel() {
25
+ this.pendingDraw = void 0;
26
+ if (this.handle === void 0) return;
27
+ this.frames.cancel(this.handle);
28
+ this.handle = void 0;
29
+ }
30
+ drawPending = () => {
31
+ const draw = this.pendingDraw;
32
+ this.pendingDraw = void 0;
33
+ this.handle = void 0;
34
+ draw?.();
35
+ };
36
+ };
37
+ //#endregion
38
+ export { AnimationFrameDraws };
@@ -0,0 +1,9 @@
1
+ //#region ../../packages/player/src/composition/animationFrames.ts
2
+ var ANIMATION_FRAMES = {
3
+ request: (callback) => requestAnimationFrame(callback),
4
+ cancel: (handle) => {
5
+ cancelAnimationFrame(handle);
6
+ }
7
+ };
8
+ //#endregion
9
+ export { ANIMATION_FRAMES };
@@ -1,5 +1,6 @@
1
1
  import { messageOf } from "../../../core/src/shared/errors/GyroViewError.js";
2
2
  import { milliseconds, seconds } from "../../../core/src/shared/units/time.js";
3
+ import { UPRIGHT_MOUNTING } from "../../../core/src/domain/motion/mounting/Mounting.js";
3
4
  import { stabilizerFor } from "../../../core/src/domain/motion/stabilization/stabilizers.js";
4
5
  import { buildStitchingSetup } from "../../../core/src/domain/stitching/StitchingSetup.js";
5
6
  import { StabilizingFrameSink } from "../../../core/src/application/stabilization/StabilizingFrameSink.js";
@@ -12,6 +13,7 @@ import { MediaSourceAudioClock } from "../../../adapters/mse-audio/src/MediaSour
12
13
  import "../../../adapters/mse-audio/src/index.js";
13
14
  import { ThreeFrameRenderer } from "../../../adapters/three/src/ThreeFrameRenderer.js";
14
15
  import "../../../adapters/three/src/index.js";
16
+ import { AnimationFrameDraws } from "./AnimationFrameDraws.js";
15
17
  import { Disposables } from "./Disposables.js";
16
18
  var DECODE_PIPELINE_OPTIONS = {
17
19
  maxPendingPackets: 4,
@@ -53,10 +55,11 @@ async function buildPipeline(parts) {
53
55
  }
54
56
  /**
55
57
  * The GPU stitcher on the host canvas, behind the stabilizing sink when there is a gyro, behind
56
- * gain matching, which measures what the stitcher drew.
58
+ * gain matching, which measures what the stitcher drew. A change of the view or the gains is
59
+ * drawn once at the next animation frame, where a frame presented in it draws it anyway.
57
60
  */
58
61
  function drawingFor(parts, disposables) {
59
- const renderer = ThreeFrameRenderer.create(parts.host.canvas, stitchingSetupOf(parts.opened));
62
+ const renderer = ThreeFrameRenderer.create(parts.host.canvas, stitchingSetupOf(parts.opened), { drawSchedule: new AnimationFrameDraws() });
60
63
  disposables.add(() => {
61
64
  renderer.dispose();
62
65
  });
@@ -127,13 +130,16 @@ function stitchingSetupOf(opened) {
127
130
  });
128
131
  }
129
132
  function sinkOver(renderer, opened) {
130
- if (!opened.motion) return {
131
- sink: renderer,
132
- stabilizing: void 0
133
- };
133
+ if (!opened.motion) {
134
+ renderer.setStabilization(UPRIGHT_MOUNTING.toBody);
135
+ return {
136
+ sink: renderer,
137
+ stabilizing: void 0
138
+ };
139
+ }
134
140
  const stabilizing = new StabilizingFrameSink({
135
141
  sink: renderer,
136
- orientations: opened.motion.orientations,
142
+ motion: opened.motion,
137
143
  frameTimes: opened.frameTimes
138
144
  });
139
145
  return {
@@ -1,10 +1,5 @@
1
+ import { ANIMATION_FRAMES } from "../composition/animationFrames.js";
1
2
  //#region ../../packages/player/src/player/FrameLoop.ts
2
- var ANIMATION_FRAMES = {
3
- request: (callback) => requestAnimationFrame(callback),
4
- cancel: (handle) => {
5
- cancelAnimationFrame(handle);
6
- }
7
- };
8
3
  /**
9
4
  * Calls `tick` once per animation frame between {@link start} and {@link stop}.
10
5
  */