@driftengine/capture 4.12.0 → 4.13.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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  A video to a playable scene, on the player's own device — no service, no account, and a runtime
4
4
  that never fetches.
5
5
 
6
- **72,960 bytes gzipped**, measured by `scripts/size-gate.test.mjs` against
6
+ **73,103 bytes gzipped**, measured by `scripts/size-gate.test.mjs` against
7
7
  `scripts/fixtures/size/capture-only.ts`. Optional: nothing in `@driftengine/core` imports it.
8
8
 
9
9
  ## What is here so far
@@ -31,5 +31,16 @@ export declare function clear(gradients: GaussianGradients, count: number): void
31
31
  * what the refinement grows the busiest of.
32
32
  */
33
33
  export declare function chain(cloud: Cloud, gradients: GaussianGradients): void;
34
- /** The fitted cloud as a capture: single precision, cut to what it holds. */
34
+ /**
35
+ * The fitted cloud as a capture: single precision, cut to what it holds, and its colour as display
36
+ * values.
37
+ *
38
+ * **Encoded, because `SplatSource.colors` is a display value** — what every trained capture stores,
39
+ * since training fits photographs as they look — and the splat shader decodes it after the
40
+ * view-dependent term. The fit runs in linear light against decoded frames, so its colour goes out
41
+ * through the sRGB encode, which the shader's decode undoes exactly. The l=1 band is carried
42
+ * through the encode's slope at that colour, the first-order step: what it gives up is a sheen as
43
+ * large as the colour itself, which comes back a little off. What would make it wrong is a fit
44
+ * run in display values, which this one is not.
45
+ */
35
46
  export declare function harvest(cloud: Cloud): SplatSource;
@@ -145,21 +145,53 @@ export function chain(cloud, gradients) {
145
145
  }
146
146
  }
147
147
  }
148
- /** The fitted cloud as a capture: single precision, cut to what it holds. */
148
+ /**
149
+ * The fitted cloud as a capture: single precision, cut to what it holds, and its colour as display
150
+ * values.
151
+ *
152
+ * **Encoded, because `SplatSource.colors` is a display value** — what every trained capture stores,
153
+ * since training fits photographs as they look — and the splat shader decodes it after the
154
+ * view-dependent term. The fit runs in linear light against decoded frames, so its colour goes out
155
+ * through the sRGB encode, which the shader's decode undoes exactly. The l=1 band is carried
156
+ * through the encode's slope at that colour, the first-order step: what it gives up is a sheen as
157
+ * large as the colour itself, which comes back a little off. What would make it wrong is a fit
158
+ * run in display values, which this one is not.
159
+ */
149
160
  export function harvest(cloud) {
150
161
  const { count } = cloud;
162
+ const linear = cloud.colors.values;
163
+ const colors = new Float32Array(count * 3);
164
+ for (let i = 0; i < count * 3; i += 1)
165
+ colors[i] = encode(Math.max(0, linear[i]));
151
166
  const source = {
152
167
  count,
153
168
  positions: Float32Array.from(cloud.positions.values.subarray(0, count * 3)),
154
169
  scales: Float32Array.from(cloud.scales.subarray(0, count * 3)),
155
170
  rotations: Float32Array.from(cloud.rotations.values.subarray(0, count * 4)),
156
- colors: Float32Array.from(cloud.colors.values.subarray(0, count * 3)),
171
+ colors,
157
172
  opacities: Float32Array.from(cloud.opacities.subarray(0, count)),
158
173
  };
159
174
  if (cloud.sh1 === null)
160
175
  return source;
161
- return {
162
- ...source,
163
- sh1: Float32Array.from(cloud.sh1.values.subarray(0, count * SH1_COEFFICIENTS)),
164
- };
176
+ const band = cloud.sh1.values;
177
+ const sh1 = new Float32Array(count * SH1_COEFFICIENTS);
178
+ /* Interleaved by basis and then by channel, so coefficient `k` carries channel `k % 3`. */
179
+ for (let at = 0; at < count; at += 1) {
180
+ for (let k = 0; k < SH1_COEFFICIENTS; k += 1) {
181
+ const slope = encodeSlope(Math.max(0, linear[at * 3 + (k % 3)]));
182
+ sh1[at * SH1_COEFFICIENTS + k] = band[at * SH1_COEFFICIENTS + k] * slope;
183
+ }
184
+ }
185
+ return { ...source, sh1 };
186
+ }
187
+ /*
188
+ * The sRGB encode and its derivative, through the reproducible `exactExp` and `exactLog` the fit's
189
+ * other transforms use, so a capture fitted on two machines hands over the same colours.
190
+ */
191
+ function encode(value) {
192
+ return value <= 0.0031308 ? value * 12.92 : 1.055 * exactExp(exactLog(value) / 2.4) - 0.055;
193
+ }
194
+ /** The encode's slope at `value`: 12.92 on its straight segment, and the curve's above it. */
195
+ function encodeSlope(value) {
196
+ return value <= 0.0031308 ? 12.92 : (1.055 / 2.4) * exactExp(exactLog(value) * (1 / 2.4 - 1));
165
197
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftengine/capture",
3
- "version": "4.12.0",
3
+ "version": "4.13.0",
4
4
  "description": "A video to a playable scene, on the player's device: the models it runs and the stages that use them",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -27,12 +27,12 @@
27
27
  ],
28
28
  "sideEffects": false,
29
29
  "peerDependencies": {
30
- "@driftengine/core": "4.12.0",
31
- "@driftengine/drft": "4.12.0",
32
- "@driftengine/nav": "4.12.0",
33
- "@driftengine/physics": "4.12.0",
34
- "@driftengine/splats": "4.12.0",
35
- "@driftengine/texture": "4.12.0"
30
+ "@driftengine/core": "4.13.0",
31
+ "@driftengine/drft": "4.13.0",
32
+ "@driftengine/nav": "4.13.0",
33
+ "@driftengine/physics": "4.13.0",
34
+ "@driftengine/splats": "4.13.0",
35
+ "@driftengine/texture": "4.13.0"
36
36
  },
37
37
  "author": "Drift Technologies",
38
38
  "repository": {
@@ -167,20 +167,53 @@ export function chain(cloud: Cloud, gradients: GaussianGradients): void {
167
167
  }
168
168
  }
169
169
 
170
- /** The fitted cloud as a capture: single precision, cut to what it holds. */
170
+ /**
171
+ * The fitted cloud as a capture: single precision, cut to what it holds, and its colour as display
172
+ * values.
173
+ *
174
+ * **Encoded, because `SplatSource.colors` is a display value** — what every trained capture stores,
175
+ * since training fits photographs as they look — and the splat shader decodes it after the
176
+ * view-dependent term. The fit runs in linear light against decoded frames, so its colour goes out
177
+ * through the sRGB encode, which the shader's decode undoes exactly. The l=1 band is carried
178
+ * through the encode's slope at that colour, the first-order step: what it gives up is a sheen as
179
+ * large as the colour itself, which comes back a little off. What would make it wrong is a fit
180
+ * run in display values, which this one is not.
181
+ */
171
182
  export function harvest(cloud: Cloud): SplatSource {
172
183
  const { count } = cloud;
184
+ const linear = cloud.colors.values;
185
+ const colors = new Float32Array(count * 3);
186
+ for (let i = 0; i < count * 3; i += 1) colors[i] = encode(Math.max(0, linear[i] as number));
173
187
  const source: SplatSource = {
174
188
  count,
175
189
  positions: Float32Array.from(cloud.positions.values.subarray(0, count * 3)),
176
190
  scales: Float32Array.from(cloud.scales.subarray(0, count * 3)),
177
191
  rotations: Float32Array.from(cloud.rotations.values.subarray(0, count * 4)),
178
- colors: Float32Array.from(cloud.colors.values.subarray(0, count * 3)),
192
+ colors,
179
193
  opacities: Float32Array.from(cloud.opacities.subarray(0, count)),
180
194
  };
181
195
  if (cloud.sh1 === null) return source;
182
- return {
183
- ...source,
184
- sh1: Float32Array.from(cloud.sh1.values.subarray(0, count * SH1_COEFFICIENTS)),
185
- };
196
+ const band = cloud.sh1.values;
197
+ const sh1 = new Float32Array(count * SH1_COEFFICIENTS);
198
+ /* Interleaved by basis and then by channel, so coefficient `k` carries channel `k % 3`. */
199
+ for (let at = 0; at < count; at += 1) {
200
+ for (let k = 0; k < SH1_COEFFICIENTS; k += 1) {
201
+ const slope = encodeSlope(Math.max(0, linear[at * 3 + (k % 3)] as number));
202
+ sh1[at * SH1_COEFFICIENTS + k] = (band[at * SH1_COEFFICIENTS + k] as number) * slope;
203
+ }
204
+ }
205
+ return { ...source, sh1 };
206
+ }
207
+
208
+ /*
209
+ * The sRGB encode and its derivative, through the reproducible `exactExp` and `exactLog` the fit's
210
+ * other transforms use, so a capture fitted on two machines hands over the same colours.
211
+ */
212
+ function encode(value: number): number {
213
+ return value <= 0.0031308 ? value * 12.92 : 1.055 * exactExp(exactLog(value) / 2.4) - 0.055;
214
+ }
215
+
216
+ /** The encode's slope at `value`: 12.92 on its straight segment, and the curve's above it. */
217
+ function encodeSlope(value: number): number {
218
+ return value <= 0.0031308 ? 12.92 : (1.055 / 2.4) * exactExp(exactLog(value) * (1 / 2.4 - 1));
186
219
  }