@bubo-squared/gyroview 0.3.1 → 0.4.1
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 +45 -0
- package/README.md +3 -3
- package/dist/index.d.ts +36 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyAudioPackager.js +49 -15
- package/dist/packages/adapters/mediabunny/src/MediabunnyCodecReader.js +3 -1
- package/dist/packages/adapters/mediabunny/src/decoderConfigurations.js +2 -2
- package/dist/packages/adapters/mediabunny/src/trackColour.js +21 -0
- package/dist/packages/adapters/mse-audio/src/SourceBufferFeeder.js +12 -1
- package/dist/packages/adapters/three/src/ThreeFrameRenderer.js +3 -0
- package/dist/packages/adapters/three/src/conversionUniforms.js +61 -0
- package/dist/packages/adapters/three/src/glslLiterals.js +10 -0
- package/dist/packages/adapters/three/src/matrixCorrections.js +35 -0
- package/dist/packages/adapters/three/src/rendererUniforms.js +33 -12
- package/dist/packages/adapters/three/src/shaderPrograms.js +4 -2
- package/dist/packages/adapters/three/src/shaders/analysis.frag.js +1 -1
- package/dist/packages/adapters/three/src/shaders/displayConversion.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensModels.js +1 -1
- package/dist/packages/adapters/three/src/shaders/lensSampling.js +1 -1
- package/dist/packages/adapters/three/src/shaders/rawLenses.frag.js +1 -1
- package/dist/packages/adapters/three/src/shaders/stitch.frag.js +1 -1
- package/dist/packages/adapters/three/src/threeMatrix.js +10 -0
- package/dist/packages/adapters/webcodecs/src/WebCodecsVideoDecoderPort.js +4 -2
- package/dist/packages/adapters/webcodecs/src/colorSpaceOf.js +32 -0
- package/dist/packages/core/src/application/recording/frameTimesOf.js +0 -1
- package/dist/packages/core/src/domain/colour/DisplayConversion.js +137 -0
- package/dist/packages/core/src/domain/colour/TrackColour.js +38 -0
- package/dist/packages/core/src/domain/colour/matrixCorrection.js +76 -0
- package/dist/packages/core/src/domain/format/calibration/CalibrationVersion.js +7 -4
- package/dist/packages/core/src/domain/format/calibration/layouts/CalibrationStringLayout.js +1 -1
- package/dist/packages/core/src/domain/format/calibration/layouts/LegacyCalibrationLayout.js +1 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/MeiCalibrationLayout.js +109 -35
- package/dist/packages/core/src/domain/format/calibration/layouts/PolynomialCalibrationLayout.js +1 -0
- package/dist/packages/core/src/domain/format/calibration/offsetTokens.js +33 -4
- package/dist/packages/core/src/domain/format/calibration/parseOffsetString.js +22 -17
- package/dist/packages/core/src/domain/format/calibration/selectCalibration.js +28 -15
- package/dist/packages/core/src/domain/format/calibration/v6TermReading.js +20 -0
- package/dist/packages/core/src/domain/format/info/calibrationSources.js +27 -0
- package/dist/packages/core/src/domain/format/info/infoFields.js +8 -2
- package/dist/packages/core/src/domain/format/info/parseInfoRecord.js +11 -6
- package/dist/packages/core/src/domain/motion/imu/ImuFrame.js +31 -9
- package/dist/packages/core/src/domain/motion/timing/FrameTimes.js +1 -3
- package/dist/packages/core/src/domain/motion/timing/frameTimeSources.js +0 -3
- package/dist/packages/core/src/domain/optics/MeiDistortion.js +43 -0
- package/dist/packages/core/src/domain/optics/MeiModel.js +6 -14
- package/dist/packages/core/src/domain/optics/scaledProjection.js +30 -0
- package/dist/packages/core/src/domain/stitching/StitchingSetup.js +13 -4
- package/dist/packages/core/src/index.js +6 -2
- package/dist/packages/core/src/shared/index.js +11 -0
- package/dist/packages/core/src/shared/mapRecord.js +11 -0
- package/dist/packages/core/src/shared/protobuf/ProtobufMessage.js +12 -26
- package/dist/packages/core/src/shared/protobuf/wireFormat.js +16 -0
- package/dist/packages/player/src/composition/buildPipeline.js +2 -1
- package/dist/packages/player/src/composition/openInputs.js +4 -1
- package/dist/standalone.js +211 -100
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,51 @@
|
|
|
3
3
|
What changed for a page using the package, newest first. Until 1.0, a minor version may change
|
|
4
4
|
the API.
|
|
5
5
|
|
|
6
|
+
## 0.4.1 (2026-10-01)
|
|
7
|
+
|
|
8
|
+
What a page may notice:
|
|
9
|
+
|
|
10
|
+
- An Insta360 X4 Air recording is stabilized about the right axes: its IMU frame is measured
|
|
11
|
+
(ADR 0009) where it was assumed. The `ready` event's `imuFrame` is
|
|
12
|
+
`{ name: 'X4 Air', isVerified: true }` where it was the unverified aligned frame, and the load
|
|
13
|
+
no longer warns (`recording-degraded`) that the IMU frame has not been verified on a recording.
|
|
14
|
+
- A camera is given a measured IMU frame only by its whole model name: a model whose name merely
|
|
15
|
+
begins with a measured camera's, such as a later "X5 Pro", gets the unverified frame and its
|
|
16
|
+
warning until a recording of it is measured.
|
|
17
|
+
|
|
18
|
+
## 0.4.0 (2026-10-01)
|
|
19
|
+
|
|
20
|
+
New:
|
|
21
|
+
|
|
22
|
+
- Insta360 X6 recordings play. Their only calibration is a v6 string, now read as the Mei model
|
|
23
|
+
with more terms (ADR 0032); the X6's IMU frame and the radial scale its lenses want are
|
|
24
|
+
measured (ADR 0009, ADR 0023); and its 10-bit HLG video is shown as SDR, as Insta360 Studio
|
|
25
|
+
shows it (ADR 0033).
|
|
26
|
+
- Each of the `ready` event's `tracks` has a `colour`: its primaries, transfer, matrix and range
|
|
27
|
+
as the track's bitstream says (`TrackColour`, with the types `ColourPrimaries`,
|
|
28
|
+
`TransferCharacteristics`, `MatrixCoefficients` and `ColourRange`).
|
|
29
|
+
- `inspectRecording`'s calibration strings include `offsetV6`, and `calibrationVersion` may be
|
|
30
|
+
6, the v6 string's.
|
|
31
|
+
|
|
32
|
+
What a page may notice:
|
|
33
|
+
|
|
34
|
+
- A recording whose only calibration is a v6 string plays where it failed with
|
|
35
|
+
`no-calibration`.
|
|
36
|
+
- Stabilization samples the gyro half way through each frame's shutter, no longer half a
|
|
37
|
+
readout later. An X5's stabilized picture shifts by that half readout (4 ms at 5.7K60, 10.6 ms
|
|
38
|
+
at 8K30), and a swinging camera's world holds stiller (ADR 0034).
|
|
39
|
+
- A track whose colour the player cannot show as it should is drawn with a
|
|
40
|
+
`recording-degraded` warning: a PQ or linear-light transfer is drawn as recorded, SDR of wider
|
|
41
|
+
primaries than BT.709's is shown as BT.709, HLG of primaries other than BT.709's or BT.2020's
|
|
42
|
+
keeps its gamut.
|
|
43
|
+
- The decoder is told the track's whole colour, not only its range (the range alone where the
|
|
44
|
+
browser's WebCodecs does not know one of its values).
|
|
45
|
+
- Where a browser's decoder converts a track's colour through another matrix than the track's
|
|
46
|
+
own, as Safari's does with the X6's BT.2020, the picture is brought back to the track's.
|
|
47
|
+
- Seeking again and again, or dragging the seek bar, no longer stops playback now and then with
|
|
48
|
+
`decode` ("the audio element failed (media error 3)"): the sound is handed to the browser in
|
|
49
|
+
whole segments, so a seek can no longer cut one in half.
|
|
50
|
+
|
|
6
51
|
## 0.3.1 (2026-09-30)
|
|
7
52
|
|
|
8
53
|
New:
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @bubo-squared/gyroview
|
|
2
2
|
|
|
3
|
-
Play raw Insta360 `.insv` recordings (X3, X4, X5) in the browser. `<gyro-view>` reads the
|
|
3
|
+
Play raw Insta360 `.insv` recordings (X3, X4, X5, X6) in the browser. `<gyro-view>` reads the
|
|
4
4
|
camera's dual-fisheye file directly, over HTTP byte ranges or from a local file, decodes both
|
|
5
5
|
lenses in hardware with WebCodecs, and stitches and gyro-stabilizes them on the GPU. No Insta360
|
|
6
6
|
Studio export step.
|
|
@@ -45,10 +45,10 @@ A page without a bundler loads the standalone file, which has Three.js and media
|
|
|
45
45
|
```html
|
|
46
46
|
<script
|
|
47
47
|
type="module"
|
|
48
|
-
src="https://cdn.jsdelivr.net/npm/@bubo-squared/gyroview@0.
|
|
48
|
+
src="https://cdn.jsdelivr.net/npm/@bubo-squared/gyroview@0.4/dist/standalone.js"
|
|
49
49
|
></script>
|
|
50
50
|
<script type="module">
|
|
51
|
-
import { inspectRecording } from 'https://cdn.jsdelivr.net/npm/@bubo-squared/gyroview@0.
|
|
51
|
+
import { inspectRecording } from 'https://cdn.jsdelivr.net/npm/@bubo-squared/gyroview@0.4/dist/standalone.js';
|
|
52
52
|
</script>
|
|
53
53
|
```
|
|
54
54
|
|
package/dist/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ declare const CalibrationVersion: {
|
|
|
42
42
|
readonly Legacy: 1;
|
|
43
43
|
readonly Polynomial: 2;
|
|
44
44
|
readonly Mei: 3;
|
|
45
|
+
readonly ExtendedMei: 6;
|
|
45
46
|
};
|
|
46
47
|
type CalibrationVersion = (typeof CalibrationVersion)[keyof typeof CalibrationVersion];
|
|
47
48
|
type Milliseconds = Brand<number, "Milliseconds">;
|
|
@@ -67,6 +68,7 @@ interface CalibrationStrings {
|
|
|
67
68
|
readonly offset: string | undefined;
|
|
68
69
|
readonly offsetV2: string | undefined;
|
|
69
70
|
readonly offsetV3: string | undefined;
|
|
71
|
+
readonly offsetV6: string | undefined;
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
72
74
|
* Everything the player learns from the info record. Absent fields are `undefined`; nothing here
|
|
@@ -225,6 +227,39 @@ export interface RecordingInspection {
|
|
|
225
227
|
readonly gyro: GyroSummary | UnreadableGyro | undefined;
|
|
226
228
|
readonly exposure: ExposureReport | undefined;
|
|
227
229
|
}
|
|
230
|
+
declare const COLOUR_PRIMARIES: readonly [
|
|
231
|
+
"bt709",
|
|
232
|
+
"bt470bg",
|
|
233
|
+
"smpte170m",
|
|
234
|
+
"bt2020",
|
|
235
|
+
"smpte432"
|
|
236
|
+
];
|
|
237
|
+
declare const TRANSFER_CHARACTERISTICS: readonly [
|
|
238
|
+
"bt709",
|
|
239
|
+
"smpte170m",
|
|
240
|
+
"iec61966-2-1",
|
|
241
|
+
"linear",
|
|
242
|
+
"pq",
|
|
243
|
+
"hlg"
|
|
244
|
+
];
|
|
245
|
+
declare const MATRIX_COEFFICIENTS: readonly [
|
|
246
|
+
"rgb",
|
|
247
|
+
"bt709",
|
|
248
|
+
"bt470bg",
|
|
249
|
+
"smpte170m",
|
|
250
|
+
"bt2020-ncl"
|
|
251
|
+
];
|
|
252
|
+
type Unspecified = "unspecified";
|
|
253
|
+
export type ColourPrimaries = (typeof COLOUR_PRIMARIES)[number] | Unspecified;
|
|
254
|
+
export type TransferCharacteristics = (typeof TRANSFER_CHARACTERISTICS)[number] | Unspecified;
|
|
255
|
+
export type MatrixCoefficients = (typeof MATRIX_COEFFICIENTS)[number] | Unspecified;
|
|
256
|
+
export type ColourRange = "full" | "limited" | Unspecified;
|
|
257
|
+
export interface TrackColour {
|
|
258
|
+
readonly primaries: ColourPrimaries;
|
|
259
|
+
readonly transfer: TransferCharacteristics;
|
|
260
|
+
readonly matrix: MatrixCoefficients;
|
|
261
|
+
readonly range: ColourRange;
|
|
262
|
+
}
|
|
228
263
|
interface VideoTrackDescription {
|
|
229
264
|
readonly trackIndex: number;
|
|
230
265
|
readonly codedWidth: number;
|
|
@@ -233,6 +268,7 @@ interface VideoTrackDescription {
|
|
|
233
268
|
* WebCodecs codec string, for example `hvc1.1.6.L153.B0` or `avc1.640033`.
|
|
234
269
|
*/
|
|
235
270
|
readonly codec: string;
|
|
271
|
+
readonly colour: TrackColour;
|
|
236
272
|
}
|
|
237
273
|
export type StabilizationMode = "off" | "lock" | "horizon" | "follow";
|
|
238
274
|
export declare const STABILIZATION_MODES: readonly StabilizationMode[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GyroViewError } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
1
|
+
import { GyroViewError, ensureInvariant } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
2
|
import "../../../core/src/index.js";
|
|
3
3
|
import { SegmentChannel } from "./SegmentChannel.js";
|
|
4
4
|
import { EncodedAudioPacketSource, EncodedPacket, Mp4OutputFormat, NullTarget, Output } from "mediabunny";
|
|
@@ -9,10 +9,9 @@ import { EncodedAudioPacketSource, EncodedPacket, Mp4OutputFormat, NullTarget, O
|
|
|
9
9
|
*/
|
|
10
10
|
var FRAGMENT_DURATION_SECONDS = 1;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* re-packaging pauses.
|
|
12
|
+
* Media segments that may wait for the consumer before re-packaging pauses.
|
|
14
13
|
*/
|
|
15
|
-
var SEGMENTS_AHEAD =
|
|
14
|
+
var SEGMENTS_AHEAD = 2;
|
|
16
15
|
/**
|
|
17
16
|
* The codec strings of the audio the cameras record, AAC (`mp4a.40.2` for AAC-LC), and mediabunny's
|
|
18
17
|
* name for it. Other codecs are not re-packaged: the picture then plays on a silent clock.
|
|
@@ -21,10 +20,10 @@ var AAC_CODEC_STRING_PREFIX = "mp4a.40.";
|
|
|
21
20
|
var AAC = "aac";
|
|
22
21
|
/**
|
|
23
22
|
* AudioPackager over mediabunny: the samples re-packaged, unchanged, into fragmented MP4.
|
|
24
|
-
* Segments are
|
|
25
|
-
* yields whole
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* Segments are assembled from the muxer's box callbacks rather than taken from its byte stream:
|
|
24
|
+
* that yields whole segments and leaves out the `mfra` index the muxer appends at the end, which
|
|
25
|
+
* Media Source Extensions do not accept. Fragmented output keeps the track's own timestamps, so
|
|
26
|
+
* segments started mid-track land at their true time.
|
|
28
27
|
*/
|
|
29
28
|
var MediabunnyAudioPackager = class {
|
|
30
29
|
segmentsOf(samples, configuration) {
|
|
@@ -112,18 +111,53 @@ function decoderConfigOf(configuration) {
|
|
|
112
111
|
...configuration.description && { description: configuration.description }
|
|
113
112
|
};
|
|
114
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* The muxer's boxes handed on as whole segments: the initialization segment (`ftyp` and `moov`)
|
|
116
|
+
* and each media segment (`moof` and its `mdat`). A consumer that stops between two segments, as
|
|
117
|
+
* a seek does, then never leaves a source buffer's parser inside one, where Chromium reads the
|
|
118
|
+
* next segments' bytes as the samples of the `moof` it holds and fails to decode them.
|
|
119
|
+
*/
|
|
115
120
|
function fragmentedMp4Into(channel) {
|
|
116
|
-
const
|
|
117
|
-
channel.push(new Uint8Array(data));
|
|
118
|
-
};
|
|
121
|
+
const segment = new SegmentAssembly(channel);
|
|
119
122
|
return new Mp4OutputFormat({
|
|
120
123
|
fastStart: "fragmented",
|
|
121
124
|
minimumFragmentDuration: FRAGMENT_DURATION_SECONDS,
|
|
122
|
-
onFtyp:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
onFtyp: (data) => {
|
|
126
|
+
segment.begin(data);
|
|
127
|
+
},
|
|
128
|
+
onMoov: (data) => {
|
|
129
|
+
segment.end(data);
|
|
130
|
+
},
|
|
131
|
+
onMoof: (data) => {
|
|
132
|
+
segment.begin(data);
|
|
133
|
+
},
|
|
134
|
+
onMdat: (data) => {
|
|
135
|
+
segment.end(data);
|
|
136
|
+
}
|
|
126
137
|
});
|
|
127
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* A segment's first box held until the box that ends it arrives, then both pushed as one.
|
|
141
|
+
*/
|
|
142
|
+
var SegmentAssembly = class {
|
|
143
|
+
channel;
|
|
144
|
+
opening;
|
|
145
|
+
constructor(channel) {
|
|
146
|
+
this.channel = channel;
|
|
147
|
+
}
|
|
148
|
+
begin(box) {
|
|
149
|
+
ensureInvariant(this.opening === void 0, "a segment began before the last one ended");
|
|
150
|
+
this.opening = new Uint8Array(box);
|
|
151
|
+
}
|
|
152
|
+
end(box) {
|
|
153
|
+
const { opening } = this;
|
|
154
|
+
ensureInvariant(opening !== void 0, "a segment ended that never began");
|
|
155
|
+
const whole = new Uint8Array(opening.byteLength + box.byteLength);
|
|
156
|
+
whole.set(opening);
|
|
157
|
+
whole.set(box, opening.byteLength);
|
|
158
|
+
this.opening = void 0;
|
|
159
|
+
this.channel.push(whole);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
128
162
|
//#endregion
|
|
129
163
|
export { MediabunnyAudioPackager };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GyroViewError, isAbortError } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
2
|
import "../../../core/src/index.js";
|
|
3
3
|
import { audioConfigurationOf, videoConfigurationOf } from "./decoderConfigurations.js";
|
|
4
|
+
import { trackColourOf } from "./trackColour.js";
|
|
4
5
|
import { ALL_FORMATS, BufferSource, Input } from "mediabunny";
|
|
5
6
|
//#region ../../packages/adapters/mediabunny/src/MediabunnyCodecReader.ts
|
|
6
7
|
/**
|
|
@@ -39,7 +40,8 @@ async function videoCodecOf(track, trackIndex) {
|
|
|
39
40
|
trackIndex,
|
|
40
41
|
codedWidth,
|
|
41
42
|
codedHeight,
|
|
42
|
-
codec: config.codec
|
|
43
|
+
codec: config.codec,
|
|
44
|
+
colour: trackColourOf(config.colorSpace)
|
|
43
45
|
};
|
|
44
46
|
return {
|
|
45
47
|
trackId: track.id,
|
|
@@ -2,7 +2,7 @@ import { copyOfBytes } from "./bufferSources.js";
|
|
|
2
2
|
//#region ../../packages/adapters/mediabunny/src/decoderConfigurations.ts
|
|
3
3
|
/**
|
|
4
4
|
* A WebCodecs video decoder configuration in the core's terms; the coded size comes from the
|
|
5
|
-
* track where the configuration leaves it out.
|
|
5
|
+
* track where the configuration leaves it out, the colour from the track's description.
|
|
6
6
|
*/
|
|
7
7
|
function videoConfigurationOf(config, description) {
|
|
8
8
|
return {
|
|
@@ -10,7 +10,7 @@ function videoConfigurationOf(config, description) {
|
|
|
10
10
|
codedWidth: config.codedWidth ?? description.codedWidth,
|
|
11
11
|
codedHeight: config.codedHeight ?? description.codedHeight,
|
|
12
12
|
description: config.description === void 0 ? void 0 : copyOfBytes(config.description),
|
|
13
|
-
|
|
13
|
+
colour: description.colour
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { COLOUR_PRIMARIES, MATRIX_COEFFICIENTS, TRANSFER_CHARACTERISTICS, namedOrUnspecified } from "../../../core/src/domain/colour/TrackColour.js";
|
|
2
|
+
import "../../../core/src/index.js";
|
|
3
|
+
//#region ../../packages/adapters/mediabunny/src/trackColour.ts
|
|
4
|
+
/**
|
|
5
|
+
* A track's colour in the core's terms, from the colour space mediabunny reads off its sample
|
|
6
|
+
* entry or, without a `colr` box, its SPS: a value the core does not name is unspecified.
|
|
7
|
+
*/
|
|
8
|
+
function trackColourOf(colorSpace) {
|
|
9
|
+
return {
|
|
10
|
+
primaries: namedOrUnspecified(COLOUR_PRIMARIES, colorSpace?.primaries),
|
|
11
|
+
transfer: namedOrUnspecified(TRANSFER_CHARACTERISTICS, colorSpace?.transfer),
|
|
12
|
+
matrix: namedOrUnspecified(MATRIX_COEFFICIENTS, colorSpace?.matrix),
|
|
13
|
+
range: rangeOf(colorSpace?.fullRange)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function rangeOf(fullRange) {
|
|
17
|
+
if (fullRange === true) return "full";
|
|
18
|
+
return fullRange === false ? "limited" : "unspecified";
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { trackColourOf };
|
|
@@ -73,7 +73,7 @@ var SourceBufferFeeder = class {
|
|
|
73
73
|
async feed(from, stop) {
|
|
74
74
|
try {
|
|
75
75
|
await this.settlePendingAppend();
|
|
76
|
-
if (this.hasAllAudioFrom(from)) return;
|
|
76
|
+
if (stop.wasStopped || this.hasElementFailed() || this.hasAllAudioFrom(from)) return;
|
|
77
77
|
await this.feedFrom(this.startOf(from), stop);
|
|
78
78
|
} catch (error) {
|
|
79
79
|
this.failureValue ??= asGyroViewError(error, "decode", "feeding the audio buffer failed");
|
|
@@ -106,11 +106,15 @@ var SourceBufferFeeder = class {
|
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
108
108
|
* Appends segments as the playhead needs them. True when the track ended, false when stopped.
|
|
109
|
+
* A run stops only between two whole segments (the port's promise) and never cancels an
|
|
110
|
+
* append, so the source buffer's parser is always at a segment's start when the next run
|
|
111
|
+
* appends its initialization segment.
|
|
109
112
|
*/
|
|
110
113
|
async pump(segments, stop) {
|
|
111
114
|
let next = await this.nextWhenNeeded(segments, stop);
|
|
112
115
|
while (next !== STOPPED) {
|
|
113
116
|
if (next.done === true) return true;
|
|
117
|
+
if (this.hasElementFailed()) return false;
|
|
114
118
|
this.evictBehind();
|
|
115
119
|
await this.append(next.value);
|
|
116
120
|
next = await this.nextWhenNeeded(segments, stop);
|
|
@@ -162,6 +166,13 @@ var SourceBufferFeeder = class {
|
|
|
162
166
|
}
|
|
163
167
|
if (await outcome === "error") throw new GyroViewError("decode", "the audio buffer could not parse a segment");
|
|
164
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* The element failed: every append now throws, and the element's own error says why, not the
|
|
171
|
+
* feeder's.
|
|
172
|
+
*/
|
|
173
|
+
hasElementFailed() {
|
|
174
|
+
return this.parts.element.error !== null;
|
|
175
|
+
}
|
|
165
176
|
async settlePendingAppend() {
|
|
166
177
|
if (this.parts.sourceBuffer.updating) await nextOfEvents(this.parts.sourceBuffer, ["updateend"]);
|
|
167
178
|
}
|
|
@@ -5,6 +5,7 @@ import { aspectOf } from "../../../core/src/domain/view/screenLayout.js";
|
|
|
5
5
|
import { DEFAULT_FRAMING } from "../../../core/src/domain/view/Framing.js";
|
|
6
6
|
import { viewModeRulesFor } from "../../../core/src/domain/view/viewModes.js";
|
|
7
7
|
import "../../../core/src/index.js";
|
|
8
|
+
import { MatrixCorrections } from "./matrixCorrections.js";
|
|
8
9
|
import { SAMPLING_STRATEGIES } from "./samplingStrategies.js";
|
|
9
10
|
import { applyLensGain, applyPicture, applyShaderSampling, applyStabilization, createRendererUniforms } from "./rendererUniforms.js";
|
|
10
11
|
import { createFullscreenTriangle } from "./fullscreenPass.js";
|
|
@@ -127,6 +128,7 @@ var ThreeFrameRenderer = class {
|
|
|
127
128
|
const frame = frames[index];
|
|
128
129
|
if (frame) texture.setFrame(frame.handle);
|
|
129
130
|
}
|
|
131
|
+
this.parts.matrixCorrections.follow(frames.map((frame) => frame.handle));
|
|
130
132
|
this.hasFrames = true;
|
|
131
133
|
this.render();
|
|
132
134
|
}
|
|
@@ -250,6 +252,7 @@ function assembleParts(renderer, setup, pictures) {
|
|
|
250
252
|
materials,
|
|
251
253
|
textures,
|
|
252
254
|
uniforms,
|
|
255
|
+
matrixCorrections: new MatrixCorrections(setup.lenses, uniforms.uLensMatrixCorrection),
|
|
253
256
|
lensCount: setup.lenses.length,
|
|
254
257
|
seamProof
|
|
255
258
|
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { IDENTITY_MATRIX3 } from "../../../core/src/shared/math/Matrix3.js";
|
|
2
|
+
import { BT709_LUMINANCE, HLG_OETF } from "../../../core/src/domain/colour/DisplayConversion.js";
|
|
3
|
+
import "../../../core/src/index.js";
|
|
4
|
+
import { toThreeMatrix } from "./threeMatrix.js";
|
|
5
|
+
import { glslFloat } from "./glslLiterals.js";
|
|
6
|
+
import { Vector4 } from "three";
|
|
7
|
+
//#region ../../packages/adapters/three/src/conversionUniforms.ts
|
|
8
|
+
/**
|
|
9
|
+
* The kinds of conversion as the shader tells them apart; it shows any kind but HLG's as
|
|
10
|
+
* recorded.
|
|
11
|
+
*/
|
|
12
|
+
var CONVERSION_AS_RECORDED = 0;
|
|
13
|
+
var CONVERSION_HLG_TO_SDR_BT709 = 1;
|
|
14
|
+
/**
|
|
15
|
+
* The constants `displayConversion.glsl` refers to: HLG's kind, BT.2100 HLG's constants and
|
|
16
|
+
* BT.709's luminance, from the core.
|
|
17
|
+
*/
|
|
18
|
+
var CONVERSION_DEFINES = [
|
|
19
|
+
["CONVERSION_HLG_TO_SDR_BT709", CONVERSION_HLG_TO_SDR_BT709],
|
|
20
|
+
["HLG_A", glslFloat(HLG_OETF.a)],
|
|
21
|
+
["HLG_B", glslFloat(HLG_OETF.b)],
|
|
22
|
+
["HLG_C", glslFloat(HLG_OETF.c)],
|
|
23
|
+
["HLG_SEGMENT_JOIN", glslFloat(HLG_OETF.segmentJoin)],
|
|
24
|
+
["HLG_SQUARE_SEGMENT_DIVISOR", glslFloat(HLG_OETF.squareSegmentDivisor)],
|
|
25
|
+
["HLG_LOG_SEGMENT_DIVISOR", glslFloat(HLG_OETF.logSegmentDivisor)],
|
|
26
|
+
["BT709_LUMINANCE_RED", glslFloat(BT709_LUMINANCE[0])],
|
|
27
|
+
["BT709_LUMINANCE_GREEN", glslFloat(BT709_LUMINANCE[1])],
|
|
28
|
+
["BT709_LUMINANCE_BLUE", glslFloat(BT709_LUMINANCE[2])]
|
|
29
|
+
];
|
|
30
|
+
function conversionUniforms(lenses) {
|
|
31
|
+
const slots = lenses.map((lens) => conversionSlotOf(lens.displayConversion));
|
|
32
|
+
return {
|
|
33
|
+
uLensConversion: { value: slots.map((slot) => slot.kind) },
|
|
34
|
+
uLensMatrixCorrection: { value: lenses.map(() => toThreeMatrix(IDENTITY_MATRIX3)) },
|
|
35
|
+
uLensGamut: { value: slots.map((slot) => slot.gamut) },
|
|
36
|
+
uLensTone: { value: slots.map((slot) => slot.tone) }
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Exhaustive over the conversion kinds: a new kind does not compile until it is packed. What a
|
|
41
|
+
* kind does not use stays neutral: the identity gamut, a tone of zeros.
|
|
42
|
+
*/
|
|
43
|
+
function conversionSlotOf(conversion) {
|
|
44
|
+
switch (conversion.kind) {
|
|
45
|
+
case "as-recorded": return {
|
|
46
|
+
kind: CONVERSION_AS_RECORDED,
|
|
47
|
+
gamut: toThreeMatrix(IDENTITY_MATRIX3),
|
|
48
|
+
tone: new Vector4()
|
|
49
|
+
};
|
|
50
|
+
case "hlg-to-sdr-bt709": {
|
|
51
|
+
const { exposure, kneeStart, ceiling, exponent } = conversion.tone;
|
|
52
|
+
return {
|
|
53
|
+
kind: CONVERSION_HLG_TO_SDR_BT709,
|
|
54
|
+
gamut: toThreeMatrix(conversion.gamut),
|
|
55
|
+
tone: new Vector4(exposure, kneeStart, ceiling, exponent)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { CONVERSION_DEFINES, conversionUniforms };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region ../../packages/adapters/three/src/glslLiterals.ts
|
|
2
|
+
/**
|
|
3
|
+
* A number as a GLSL float literal, with its decimal point: three.js writes a define's number as
|
|
4
|
+
* JavaScript prints it, so 3 would be an int, and GLSL ES 3.00 converts no int to a float.
|
|
5
|
+
*/
|
|
6
|
+
function glslFloat(value) {
|
|
7
|
+
return Number.isSafeInteger(value) ? value.toFixed(1) : String(value);
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { glslFloat };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { MATRIX_COEFFICIENTS, namedOrUnspecified } from "../../../core/src/domain/colour/TrackColour.js";
|
|
2
|
+
import { matrixCorrectionOf } from "../../../core/src/domain/colour/matrixCorrection.js";
|
|
3
|
+
import "../../../core/src/index.js";
|
|
4
|
+
import { toThreeMatrix } from "./threeMatrix.js";
|
|
5
|
+
//#region ../../packages/adapters/three/src/matrixCorrections.ts
|
|
6
|
+
/**
|
|
7
|
+
* Follows the matrix each lens's frames name, and brings their texels back to the matrix the
|
|
8
|
+
* track was recorded with where the two differ (ADR 0033): WebKit's decoders convert Y′CbCr with
|
|
9
|
+
* BT.709 whatever a stream says, and name BT.709 on the frames they make.
|
|
10
|
+
*/
|
|
11
|
+
var MatrixCorrections = class {
|
|
12
|
+
lenses;
|
|
13
|
+
uniform;
|
|
14
|
+
named;
|
|
15
|
+
constructor(lenses, uniform) {
|
|
16
|
+
this.lenses = lenses;
|
|
17
|
+
this.uniform = uniform;
|
|
18
|
+
this.named = Array.from({ length: lenses.length });
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Updates a lens's correction when its frames name another matrix than before.
|
|
22
|
+
*/
|
|
23
|
+
follow(frames) {
|
|
24
|
+
for (const [index, lens] of this.lenses.entries()) {
|
|
25
|
+
const frame = frames[lens.frameSlot];
|
|
26
|
+
if (!frame) continue;
|
|
27
|
+
const named = namedOrUnspecified(MATRIX_COEFFICIENTS, frame.colorSpace.matrix);
|
|
28
|
+
if (named === this.named[index]) continue;
|
|
29
|
+
this.named[index] = named;
|
|
30
|
+
this.uniform.value[index] = toThreeMatrix(matrixCorrectionOf(lens.displayConversion.matrix, named));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
export { MatrixCorrections };
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { ensureIndexInRange, ensureInvariant } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import { MEI_TERM_CAPACITY } from "../../../core/src/domain/optics/MeiDistortion.js";
|
|
2
3
|
import { IDENTITY_MATRIX3 } from "../../../core/src/shared/math/Matrix3.js";
|
|
3
4
|
import { aspectOfArea, planeHalfExtentOf } from "../../../core/src/domain/view/rectilinear.js";
|
|
4
5
|
import "../../../core/src/index.js";
|
|
6
|
+
import { toThreeMatrix } from "./threeMatrix.js";
|
|
7
|
+
import { CONVERSION_DEFINES, conversionUniforms } from "./conversionUniforms.js";
|
|
5
8
|
import "./samplingStrategies.js";
|
|
6
9
|
import { Matrix3, Vector2, Vector3, Vector4 } from "three";
|
|
7
10
|
var LENS_MEI = 0;
|
|
@@ -14,6 +17,10 @@ var SHADER_DEFINES = Object.fromEntries([
|
|
|
14
17
|
["MAX_LENSES", 2],
|
|
15
18
|
["LENS_MEI", LENS_MEI],
|
|
16
19
|
["LENS_RADIAL_POLYNOMIAL", LENS_RADIAL_POLYNOMIAL],
|
|
20
|
+
["MEI_RADIAL_TERMS", MEI_TERM_CAPACITY.radial],
|
|
21
|
+
["MEI_TANGENTIAL_ORDERS", MEI_TERM_CAPACITY.tangential],
|
|
22
|
+
["MEI_THIN_PRISM_ORDERS", MEI_TERM_CAPACITY.thinPrism],
|
|
23
|
+
...CONVERSION_DEFINES,
|
|
17
24
|
["SAMPLING_BILINEAR", 0],
|
|
18
25
|
["SAMPLING_TRILINEAR", 1],
|
|
19
26
|
["SAMPLING_SUPERSAMPLED", 2]
|
|
@@ -24,6 +31,7 @@ function createRendererUniforms(setup, textures) {
|
|
|
24
31
|
...viewUniforms(setup),
|
|
25
32
|
...projectionUniforms(lenses),
|
|
26
33
|
...samplingUniforms(lenses),
|
|
34
|
+
...conversionUniforms(lenses),
|
|
27
35
|
uTexture0: { value: textures[0] ?? null },
|
|
28
36
|
uTexture1: { value: textures[1] ?? null }
|
|
29
37
|
};
|
|
@@ -47,12 +55,18 @@ function projectionUniforms(lenses) {
|
|
|
47
55
|
uLensPrincipalPoint: { value: lenses.map((lens) => toVector2(lens.projection.principalPoint)) },
|
|
48
56
|
uLensFocal: { value: slots.map((slot) => slot.focal) },
|
|
49
57
|
uLensXi: { value: slots.map((slot) => slot.xi) },
|
|
50
|
-
uLensRadial: { value: slots.
|
|
51
|
-
uLensTangential: { value: slots.
|
|
58
|
+
uLensRadial: { value: slots.flatMap((slot) => slot.radial) },
|
|
59
|
+
uLensTangential: { value: slots.flatMap((slot) => slot.tangential) },
|
|
60
|
+
uLensThinPrism: { value: slots.flatMap((slot) => slot.thinPrism) },
|
|
52
61
|
uLensPolynomial: { value: slots.map((slot) => slot.polynomial) },
|
|
53
62
|
uLensHalfFov: { value: lenses.map((lens) => lens.halfFieldOfView) }
|
|
54
63
|
};
|
|
55
64
|
}
|
|
65
|
+
var NO_MEI_DISTORTION = {
|
|
66
|
+
radial: [],
|
|
67
|
+
tangential: [],
|
|
68
|
+
thinPrism: []
|
|
69
|
+
};
|
|
56
70
|
/**
|
|
57
71
|
* Exhaustive over the projection kinds: a new kind does not compile until it is packed.
|
|
58
72
|
*/
|
|
@@ -62,20 +76,33 @@ function slotOf(projection) {
|
|
|
62
76
|
kind: LENS_MEI,
|
|
63
77
|
focal: new Vector2(...projection.focal),
|
|
64
78
|
xi: projection.xi,
|
|
65
|
-
|
|
66
|
-
tangential: new Vector2(...projection.tangential),
|
|
79
|
+
...meiTermSlots(projection.distortion),
|
|
67
80
|
polynomial: new Vector4()
|
|
68
81
|
};
|
|
69
82
|
case "radial-polynomial": return {
|
|
70
83
|
kind: LENS_RADIAL_POLYNOMIAL,
|
|
71
84
|
focal: new Vector2(),
|
|
72
85
|
xi: 0,
|
|
73
|
-
|
|
74
|
-
tangential: new Vector2(),
|
|
86
|
+
...meiTermSlots(NO_MEI_DISTORTION),
|
|
75
87
|
polynomial: new Vector4(...projection.coefficients)
|
|
76
88
|
};
|
|
77
89
|
}
|
|
78
90
|
}
|
|
91
|
+
function meiTermSlots(distortion) {
|
|
92
|
+
return {
|
|
93
|
+
radial: fittedToCapacity(distortion.radial, MEI_TERM_CAPACITY.radial, () => 0),
|
|
94
|
+
tangential: fittedToCapacity(distortion.tangential.map((pair) => new Vector2(pair.p1, pair.p2)), MEI_TERM_CAPACITY.tangential, () => new Vector2()),
|
|
95
|
+
thinPrism: fittedToCapacity(distortion.thinPrism.map((term) => new Vector2(term.x, term.y)), MEI_TERM_CAPACITY.thinPrism, () => new Vector2())
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The terms in the room the shader holds for their family, the rest of it zero; more terms than
|
|
100
|
+
* it holds are a defect of the model that made them.
|
|
101
|
+
*/
|
|
102
|
+
function fittedToCapacity(terms, capacity, zero) {
|
|
103
|
+
ensureInvariant(terms.length <= capacity, `a Mei lens has ${terms.length} terms of a family the shader holds ${capacity} of`);
|
|
104
|
+
return Array.from({ length: capacity }, (_unused, index) => terms[index] ?? zero());
|
|
105
|
+
}
|
|
79
106
|
function samplingUniforms(lenses) {
|
|
80
107
|
return {
|
|
81
108
|
uLensWindow: { value: lenses.map((lens) => new Vector4(lens.window.x, lens.window.y, lens.window.width, lens.window.height)) },
|
|
@@ -136,12 +163,6 @@ function valuesOf(picture, viewportAspect) {
|
|
|
136
163
|
function toVector4(area) {
|
|
137
164
|
return new Vector4(area.x, area.y, area.width, area.height);
|
|
138
165
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Row-major core matrix into a three matrix, whose `set` takes rows too.
|
|
141
|
-
*/
|
|
142
|
-
function toThreeMatrix(m) {
|
|
143
|
-
return new Matrix3().set(...m);
|
|
144
|
-
}
|
|
145
166
|
function toVector2(point) {
|
|
146
167
|
return new Vector2(point.x, point.y);
|
|
147
168
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import analysis_frag_default from "./shaders/analysis.frag.js";
|
|
2
|
+
import displayConversion_default from "./shaders/displayConversion.js";
|
|
2
3
|
import equirectangularRays_default from "./shaders/equirectangularRays.js";
|
|
3
4
|
import fixedJoin_default from "./shaders/fixedJoin.js";
|
|
4
5
|
import header_default from "./shaders/header.js";
|
|
@@ -14,8 +15,9 @@ import stitch_frag_default from "./shaders/stitch.frag.js";
|
|
|
14
15
|
* The fragment programs, each the GLSL chunks it is made of in the order they must appear: a
|
|
15
16
|
* chunk comes after every chunk whose declarations it uses. The only place that order is known.
|
|
16
17
|
*/
|
|
18
|
+
var LENS_TEXELS = [lensTextures_default, displayConversion_default];
|
|
17
19
|
var LENS_PROJECTION = [
|
|
18
|
-
|
|
20
|
+
...LENS_TEXELS,
|
|
19
21
|
lensModels_default,
|
|
20
22
|
lensSampling_default
|
|
21
23
|
];
|
|
@@ -48,7 +50,7 @@ function pictureProgramsWith(seamJoin) {
|
|
|
48
50
|
"lens-tiles": [
|
|
49
51
|
header_default,
|
|
50
52
|
screenAreas_default,
|
|
51
|
-
|
|
53
|
+
...LENS_TEXELS,
|
|
52
54
|
rawLenses_frag_default
|
|
53
55
|
]
|
|
54
56
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
//#region ../../packages/adapters/three/src/shaders/analysis.frag.glsl?raw
|
|
2
|
-
var analysis_frag_default = "in vec2 vNdc;\nout vec4 outColor;\n\n// One row per lens: what that lens sees along the seam ring, the circle of directions halfway\n// through the blend band around lens 0's optical axis, sampled around the ring along x. Rows\n// share their directions, so their means differ by exposure (and parallax), not by content
|
|
2
|
+
var analysis_frag_default = "in vec2 vNdc;\nout vec4 outColor;\n\n// One row per lens: what that lens sees along the seam ring, the circle of directions halfway\n// through the blend band around lens 0's optical axis, sampled around the ring along x. Rows\n// share their directions, so their means differ by exposure (and parallax), not by content:\n// exposure signals, in which it is a factor (ADR 0033). Alpha marks the texels a lens images.\nvoid main() {\n int lens = int(floor((vNdc.y + 1.0) * 0.5 * float(MAX_LENSES)));\n float phi = (vNdc.x + 1.0) * PI;\n float ring = 0.5 * (uFeather.x + uFeather.y);\n vec3 inLensZero = vec3(sin(ring) * cos(phi), sin(ring) * sin(phi), cos(ring));\n // The lens rotations take body directions into each lens; the transpose goes back.\n vec3 dirBody = transpose(uLensRotation[0]) * inLensZero;\n if (lens >= uLensCount) {\n outColor = vec4(0.0);\n return;\n }\n LensSample seen = sampleLensWith(lens, dirBody, SAMPLING_BILINEAR);\n outColor = seen.isImaged ? vec4(seen.signal, 1.0) : vec4(0.0);\n}\n";
|
|
3
3
|
//#endregion
|
|
4
4
|
export { analysis_frag_default as default };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
//#region ../../packages/adapters/three/src/shaders/displayConversion.glsl?raw
|
|
2
|
+
var displayConversion_default = "// How lens i's texels are brought to the SDR BT.709 display, as the core's exposureSignalOf and\n// shownOf (ADR 0033): the browser's upload has applied the track's matrix and range; the\n// transfer and primaries are this chunk's. Shown as recorded, or HLG converted to scene light,\n// into BT.709 and toned; gain matching scales the exposure signal between the two (ADR 0012).\nuniform int uLensConversion[MAX_LENSES];\n// R′G′B′ through the matrix a frame names back to the track's own (matrixCorrectionOf).\nuniform mat3 uLensMatrixCorrection[MAX_LENSES];\nuniform mat3 uLensGamut[MAX_LENSES];\n// Exposure, roll-off start, roll-off ceiling, display encoding power.\nuniform vec4 uLensTone[MAX_LENSES];\n\nstruct ToneCurve {\n float exposure;\n float kneeStart;\n float ceiling;\n float exponent;\n};\n\nToneCurve toneOf(int i) {\n vec4 tone = uLensTone[i];\n return ToneCurve(tone.x, tone.y, tone.z, tone.w);\n}\n\nfloat hlgInverseOetf(float signal) {\n return signal <= HLG_SEGMENT_JOIN\n ? signal * signal / HLG_SQUARE_SEGMENT_DIVISOR\n : (exp((signal - HLG_C) / HLG_A) + HLG_B) / HLG_LOG_SEGMENT_DIVISOR;\n}\n\nconst vec3 BT709_LUMINANCE = vec3(BT709_LUMINANCE_RED, BT709_LUMINANCE_GREEN, BT709_LUMINANCE_BLUE);\n\nfloat rolledOff(float luminance, ToneCurve tone) {\n float over = luminance - tone.kneeStart;\n return over <= 0.0\n ? luminance\n : tone.kneeStart + over / (1.0 + over / (tone.ceiling - tone.kneeStart));\n}\n\n// The signal exposed back to light, its luminance rolled off with every channel scaled alike,\n// and encoded for the display.\nvec3 tonedFrom(vec3 signal, ToneCurve tone) {\n vec3 light = tone.exposure * pow(signal, vec3(tone.exponent));\n float luminance = dot(light, BT709_LUMINANCE);\n float scale = luminance > 0.0 ? rolledOff(luminance, tone) / luminance : 1.0;\n return min(pow(light * scale, vec3(1.0 / tone.exponent)), vec3(1.0));\n}\n\nbool isHlg(int i) {\n return uLensConversion[i] == CONVERSION_HLG_TO_SDR_BT709;\n}\n\n// Lens i's texel where its exposure is a factor; as recorded for any kind but HLG.\nvec3 exposureSignalOf(int i, vec3 texel) {\n vec3 recorded = uLensMatrixCorrection[i] * texel;\n if (!isHlg(i)) return recorded;\n // A corrected texel may fall just below black, which the square segment would make light.\n recorded = max(recorded, 0.0);\n vec3 scene = vec3(\n hlgInverseOetf(recorded.r),\n hlgInverseOetf(recorded.g),\n hlgInverseOetf(recorded.b)\n );\n vec3 light = max(uLensGamut[i] * scene, 0.0);\n return pow(light, vec3(1.0 / toneOf(i).exponent));\n}\n\n// An exposure signal of lens i as the display shows it; as recorded for any kind but HLG.\nvec3 shownOf(int i, vec3 signal) {\n if (!isHlg(i)) return signal;\n return tonedFrom(signal, toneOf(i));\n}\n\nvec3 toDisplay(int i, vec3 texel) {\n return shownOf(i, exposureSignalOf(i, texel));\n}\n";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { displayConversion_default as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
//#region ../../packages/adapters/three/src/shaders/lensModels.glsl?raw
|
|
2
|
-
var lensModels_default = "// The depth is positive for every direction inside a lens's field; directions behind it are\n// projected too, for the footprint's derivatives, and must not divide by zero.\nconst float MIN_DEPTH = 1e-3;\n\
|
|
2
|
+
var lensModels_default = "// The depth is positive for every direction inside a lens's field; directions behind it are\n// projected too, for the footprint's derivatives, and must not divide by zero.\nconst float MIN_DEPTH = 1e-3;\n\nuniform vec2 uLensPrincipalPoint[MAX_LENSES];\nuniform vec2 uLensFocal[MAX_LENSES];\nuniform float uLensXi[MAX_LENSES];\n// Each Mei lens's distortion as the core's MeiDistortion holds it, lens after lens, as many terms\n// per lens as each family holds room for, by increasing order: radial terms k1, k2, ... of r²,\n// Brown's tangential pairs (p1, p2) scaled by r^(2j), and thin-prism shifts scaled by r^(2j + 2).\n// The orders a lens does not fill are zero.\nuniform float uLensRadial[MAX_LENSES * MEI_RADIAL_TERMS];\nuniform vec2 uLensTangential[MAX_LENSES * MEI_TANGENTIAL_ORDERS];\nuniform vec2 uLensThinPrism[MAX_LENSES * MEI_THIN_PRISM_ORDERS];\nuniform vec4 uLensPolynomial[MAX_LENSES];\n\n// c0 + c1 r² + c2 r⁴ + ... of each of lens i's families, by Horner's rule, as the core's seriesIn.\nfloat radialSeries(int i, float r2) {\n float sum = 0.0;\n for (int j = MEI_RADIAL_TERMS - 1; j >= 0; j--) {\n sum = sum * r2 + uLensRadial[i * MEI_RADIAL_TERMS + j];\n }\n return sum;\n}\n\nvec2 tangentialSeries(int i, float r2) {\n vec2 sum = vec2(0.0);\n for (int j = MEI_TANGENTIAL_ORDERS - 1; j >= 0; j--) {\n sum = sum * r2 + uLensTangential[i * MEI_TANGENTIAL_ORDERS + j];\n }\n return sum;\n}\n\nvec2 thinPrismSeries(int i, float r2) {\n vec2 sum = vec2(0.0);\n for (int j = MEI_THIN_PRISM_ORDERS - 1; j >= 0; j--) {\n sum = sum * r2 + uLensThinPrism[i * MEI_THIN_PRISM_ORDERS + j];\n }\n return sum;\n}\n\n// A normalised image-plane point distorted by lens i, as the core's distortMei.\nvec2 distortMei(int i, vec2 m) {\n float r2 = dot(m, m);\n float radialFactor = 1.0 + r2 * radialSeries(i, r2);\n vec2 decentering = tangentialSeries(i, r2);\n vec2 tangentialTerm = vec2(\n 2.0 * decentering.x * m.x * m.y + decentering.y * (r2 + 2.0 * m.x * m.x),\n decentering.x * (r2 + 2.0 * m.y * m.y) + 2.0 * decentering.y * m.x * m.y\n );\n return radialFactor * m + tangentialTerm + r2 * thinPrismSeries(i, r2);\n}\n\n// Canvas pixel of a unit direction in lens i's frame under the unified (Mei) model: project onto\n// the unit sphere, then from xi behind its centre onto the image plane, distort, scale.\nvec2 projectMei(int i, vec3 d) {\n float depth = max(d.z + uLensXi[i], MIN_DEPTH);\n return uLensFocal[i] * distortMei(i, d.xy / depth) + uLensPrincipalPoint[i];\n}\n\n// Canvas pixel of a unit direction under lens i's radial polynomial in the angle from its axis.\nvec2 projectRadialPolynomial(int i, vec3 d, float theta) {\n vec4 c = uLensPolynomial[i];\n float radius = theta * (c.x + theta * (c.y + theta * (c.z + theta * c.w)));\n float lateral = length(d.xy);\n vec2 unit = lateral > 0.0 ? d.xy / lateral : vec2(0.0);\n return uLensPrincipalPoint[i] + radius * unit;\n}\n";
|
|
3
3
|
//#endregion
|
|
4
4
|
export { lensModels_default as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
//#region ../../packages/adapters/three/src/shaders/lensSampling.glsl?raw
|
|
2
|
-
var lensSampling_default = "// How a body direction becomes a texel of one lens, for the stitch and the seam meters: the\n//
|
|
2
|
+
var lensSampling_default = "// How a body direction becomes a texel of one lens, for the stitch and the seam meters: the\n// pose and field as uniforms, and the sampling through each lens model, the texel as its\n// exposure signal. Needs lensTextures.glsl, displayConversion.glsl and lensModels.glsl.\nuniform vec2 uFeather;\nuniform mat3 uLensRotation[MAX_LENSES];\nuniform int uLensKind[MAX_LENSES];\nuniform float uLensHalfFov[MAX_LENSES];\nuniform vec4 uLensWindow[MAX_LENSES];\n\nstruct LensSample {\n bool isImaged;\n // Angle from the lens's optical axis.\n float theta;\n // The texel where the lens's exposure is a factor (displayConversion.glsl).\n vec3 signal;\n};\n\n// Where lens i images direction d on its calibration canvas; the principal point, and not\n// known, for a kind of lens this shader does not know.\nvec2 canvasPixel(int i, vec3 d, float theta, out bool isKnown) {\n isKnown = true;\n if (uLensKind[i] == LENS_MEI) {\n return projectMei(i, d);\n }\n if (uLensKind[i] == LENS_RADIAL_POLYNOMIAL) {\n return projectRadialPolynomial(i, d, theta);\n }\n isKnown = false;\n return uLensPrincipalPoint[i];\n}\n\n// What lens i shows in a body direction, read as `sampling` says, if it images it at all:\n// outside its field, or beyond the window the frame shows, nothing. The footprint is taken in\n// every fragment before the gates, so its derivatives are defined.\nLensSample sampleLensWith(int i, vec3 dirBody, int sampling) {\n vec3 d = uLensRotation[i] * dirBody;\n // atan keeps its precision near the axis where acos loses it.\n float theta = atan(length(d.xy), d.z);\n bool isKnown;\n vec2 pixel = canvasPixel(i, d, theta, isKnown);\n vec2 windowUv = (pixel - uLensWindow[i].xy) / uLensWindow[i].zw;\n Footprint footprint = footprintOf(i, windowUv);\n bool isInWindow =\n all(greaterThanEqual(windowUv, vec2(0.0))) && all(lessThanEqual(windowUv, vec2(1.0)));\n LensSample result;\n result.theta = theta;\n result.isImaged = isKnown && theta < uLensHalfFov[i] && isInWindow;\n result.signal = result.isImaged\n ? exposureSignalOf(i, sampleLens(uLensTexture[i], footprint, sampling).rgb)\n : vec3(0.0);\n return result;\n}\n\n// What lens i shows in a body direction, read as the picture is.\nLensSample sampleLensAt(int i, vec3 dirBody) {\n return sampleLensWith(i, dirBody, uSampling);\n}\n";
|
|
3
3
|
//#endregion
|
|
4
4
|
export { lensSampling_default as default };
|