@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.
- package/CHANGELOG.md +54 -0
- package/README.md +6 -6
- package/dist/index.d.ts +56 -15
- package/dist/packages/adapters/mediabunny/src/MediabunnyCodecReader.js +2 -2
- package/dist/packages/adapters/mediabunny/src/decoderConfigurations.js +20 -4
- package/dist/packages/core/src/application/recording/motionOf.js +5 -1
- package/dist/packages/core/src/application/stabilization/StabilizingFrameSink.js +4 -2
- package/dist/packages/core/src/domain/format/mp4/hevcCodecString.js +161 -0
- package/dist/packages/core/src/domain/motion/imu/ImuFrame.js +35 -3
- package/dist/packages/core/src/domain/motion/mounting/Mounting.js +117 -0
- package/dist/packages/core/src/domain/motion/orientation/OrientationTrack.js +4 -2
- package/dist/packages/core/src/domain/motion/orientation/gravity.js +1 -1
- package/dist/packages/core/src/domain/view/Framing.js +8 -2
- package/dist/packages/core/src/domain/view/ViewState.js +13 -8
- package/dist/packages/core/src/domain/view/lensTilesView.js +2 -1
- package/dist/packages/core/src/domain/view/motionLookView.js +103 -0
- package/dist/packages/core/src/domain/view/normalView.js +12 -5
- package/dist/packages/core/src/domain/view/panoramaView.js +2 -1
- package/dist/packages/core/src/domain/view/screenLook.js +74 -0
- package/dist/packages/core/src/domain/view/viewModes.js +18 -1
- package/dist/packages/core/src/index.js +8 -3
- package/dist/packages/core/src/shared/binary/ByteReader.js +4 -0
- package/dist/packages/core/src/shared/index.js +1 -0
- package/dist/packages/core/src/shared/math/Matrix3.js +29 -1
- package/dist/packages/core/src/shared/math/minimum.js +17 -0
- package/dist/packages/player/src/browserPlayer.js +6 -4
- package/dist/packages/player/src/composition/BrowserAttitudeSensor.js +192 -0
- package/dist/packages/player/src/composition/attitudeSensor.js +18 -0
- package/dist/packages/player/src/composition/buildPipeline.js +9 -5
- package/dist/packages/player/src/controls/MotionLookButton.js +35 -0
- package/dist/packages/player/src/controls/controlParts.js +1 -0
- package/dist/packages/player/src/controls/controls.js +1 -1
- package/dist/packages/player/src/controls/controlsBar.js +5 -3
- package/dist/packages/player/src/controls/controlsMarkup.js +1 -0
- package/dist/packages/player/src/controls/icons.js +1 -0
- package/dist/packages/player/src/controls/messages.js +1 -0
- package/dist/packages/player/src/element/GyroViewElement.js +21 -0
- package/dist/packages/player/src/player/AutomaticStarts.js +54 -0
- package/dist/packages/player/src/player/MotionLook.js +150 -0
- package/dist/packages/player/src/player/Player.js +46 -39
- package/dist/packages/player/src/player/PlayerEvents.js +1 -0
- package/dist/packages/player/src/player/PlayerView.js +44 -13
- package/dist/packages/player/src/player/ensureFinite.js +2 -1
- package/dist/packages/player/src/player/viewAngles.js +17 -0
- package/dist/standalone.js +115 -62
- 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,
|
|
28
|
-
*
|
|
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;
|
|
@@ -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,
|
|
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:
|
|
41
|
-
*
|
|
42
|
-
* rotation about x tilts the forward direction
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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,
|
|
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
|
|
7
|
-
* on the host's canvas and sounding through its audio element. What
|
|
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
|