@driftengine/splats 3.63.0 → 4.0.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/NOTICE CHANGED
@@ -7,3 +7,23 @@ This product includes software developed at Drift Technologies
7
7
  Licensed under the Apache License, Version 2.0. Section 4(d) of that licence
8
8
  requires this NOTICE to be reproduced in any derivative work you distribute,
9
9
  in the source, the documentation, or a display generated by the work.
10
+
11
+ Models run by DriftCapture
12
+ --------------------------
13
+ DriftCapture runs the models below. Their weights are not in this repository
14
+ and are not distributed with it: tools/capture-weights/ fetches each at the
15
+ revision its manifest pins, verifies it, and converts it, and a game that
16
+ ships one ships it under that model's own licence. Each model's definition in
17
+ this engine is written from its project's published code.
18
+
19
+ - Depth Anything 3 Small and Depth Anything 3 Base. Apache License 2.0.
20
+ Copyright (c) 2025 ByteDance Ltd. and/or its affiliates.
21
+ - Depth Anything V2 Small. Apache License 2.0. The University of Hong Kong
22
+ and TikTok; converted to the Transformers layout by The HuggingFace Team.
23
+ - MobileSAM. Apache License 2.0. Its image encoder is TinyViT, Copyright (c)
24
+ 2022 Microsoft; its prompt encoder and mask decoder are Segment Anything's,
25
+ Copyright (c) Meta Platforms, Inc. and affiliates.
26
+ - SAM 2.1 tiny. Apache License 2.0. Copyright (c) Meta Platforms, Inc. and
27
+ affiliates.
28
+ - OWLv2 base patch16. Apache License 2.0. Copyright 2023 Google AI and The
29
+ HuggingFace Team.
@@ -1,4 +1,4 @@
1
- /** The two matrix operations this package needs, written once so no two callers disagree. */
1
+ /** The matrix operations this package needs, written once so no two callers disagree. */
2
2
  /**
3
3
  * `out = a * b`, column-major, sixteen multiply-adds and no dependency.
4
4
  *
@@ -27,3 +27,15 @@ export declare function multiplyMat4(out: Float32Array, a: ArrayLike<number>, b:
27
27
  * once per splat times six.
28
28
  */
29
29
  export declare function cameraInCaptureSpace(out: Float32Array, view: ArrayLike<number>, model: ArrayLike<number>): void;
30
+ /**
31
+ * The projection a frame draws a capture with: the caller's, moved by the frame's jitter, then
32
+ * corrected for the backend. `scratch` holds sixteen floats and is overwritten.
33
+ *
34
+ * **In that order, because the jitter is stated in the camera's own convention** — see
35
+ * `PrepareContext.jitter`. A reconstructed frame un-jitters every sample it takes, so a capture
36
+ * drawn without the offset is placed up to half a render pixel from where the resolve looks for it,
37
+ * differently each frame; and the correction negates y, so the same offset applied after it lands
38
+ * on the other side of the pixel. A frame that is not reconstructed hands a zero jitter, and the
39
+ * result is the corrected projection it always was.
40
+ */
41
+ export declare function projectionForFrame(out: Float32Array, correction: ArrayLike<number>, projection: ArrayLike<number>, jitter: ArrayLike<number>, scratch: Float32Array): void;
@@ -1,4 +1,5 @@
1
- /** The two matrix operations this package needs, written once so no two callers disagree. */
1
+ /** The matrix operations this package needs, written once so no two callers disagree. */
2
+ import { jitterClip } from '@driftengine/core';
2
3
  /**
3
4
  * `out = a * b`, column-major, sixteen multiply-adds and no dependency.
4
5
  *
@@ -66,3 +67,18 @@ export function cameraInCaptureSpace(out, view, model) {
66
67
  }
67
68
  /** Scratch for the product above, so a frame allocates nothing. */
68
69
  const PRODUCT = new Float32Array(16);
70
+ /**
71
+ * The projection a frame draws a capture with: the caller's, moved by the frame's jitter, then
72
+ * corrected for the backend. `scratch` holds sixteen floats and is overwritten.
73
+ *
74
+ * **In that order, because the jitter is stated in the camera's own convention** — see
75
+ * `PrepareContext.jitter`. A reconstructed frame un-jitters every sample it takes, so a capture
76
+ * drawn without the offset is placed up to half a render pixel from where the resolve looks for it,
77
+ * differently each frame; and the correction negates y, so the same offset applied after it lands
78
+ * on the other side of the pixel. A frame that is not reconstructed hands a zero jitter, and the
79
+ * result is the corrected projection it always was.
80
+ */
81
+ export function projectionForFrame(out, correction, projection, jitter, scratch) {
82
+ jitterClip(scratch, projection, jitter);
83
+ multiplyMat4(out, correction, scratch);
84
+ }
package/dist/splatPass.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /** A splat capture as a pass a consumer registers, drawn by whichever backend it is handed. */
2
2
  import { SPLAT_BINDINGS } from './shaders/generated/splat.wgsl.js';
3
3
  import { SPLAT_STRIDE, splatRows, splatTexels } from './splatLayout.js';
4
- import { cameraInCaptureSpace, multiplyMat4 } from './splatMatrix.js';
4
+ import { cameraInCaptureSpace, multiplyMat4, projectionForFrame } from './splatMatrix.js';
5
5
  import { splatBoundsVisible } from './splatCull.js';
6
6
  import { createWebgl2Splats, disposeWebgl2Splats, drawWebgl2Splats, uploadWebgl2Order, uploadWebgl2SplatRange, } from './splatGl.js';
7
7
  import { createGpuSplats, disposeGpuSplats, uploadGpuOrder, uploadGpuSplatRange, } from './splatGpu.js';
@@ -31,6 +31,12 @@ export function createSplatPass(splats, label = 'splats') {
31
31
  const view = new Float32Array(16);
32
32
  /** The caller's projection, pre-multiplied by the backend's clip correction. */
33
33
  const projection = new Float32Array(16);
34
+ /**
35
+ * The caller's projection as given, kept so a WebGPU frame can move it by the frame's jitter
36
+ * before correcting it. See `projectionForFrame`.
37
+ */
38
+ const callerProjection = new Float32Array(IDENTITY);
39
+ const jitterScratch = new Float32Array(16);
34
40
  /**
35
41
  * Where the camera is in the capture's own space, which is where the harmonics were trained.
36
42
  *
@@ -81,6 +87,7 @@ export function createSplatPass(splats, label = 'splats') {
81
87
  },
82
88
  setView(next) {
83
89
  view.set(next.view);
90
+ callerProjection.set(next.projection);
84
91
  multiplyMat4(projection, clipCorrection, next.projection);
85
92
  viewportX = next.widthPx;
86
93
  viewportY = next.heightPx;
@@ -175,6 +182,8 @@ export function createSplatPass(splats, label = 'splats') {
175
182
  uploadGpuOrder(device, gpu, pendingOrder, paddedOrder);
176
183
  pendingOrder = null;
177
184
  }
185
+ /* The frame's jitter, which is zero unless the frame is reconstructed. */
186
+ projectionForFrame(projection, clipCorrection, callerProjection, ctx.jitter, jitterScratch);
178
187
  writeVertexUniforms(gpu);
179
188
  device.queue.writeBuffer(gpu.vertexUniforms, 0, gpu.vertexScratch);
180
189
  gpu.fragmentInts[FRAG.fields.uOutputTransform.offset / 4] = ctx.outputTransform;
@@ -77,12 +77,15 @@ export function createSplatSortWorker(sortSource) {
77
77
  return sortOnMainThread;
78
78
  };
79
79
  try {
80
- if (typeof Worker === 'undefined' ||
80
+ if (
81
+ // platform: feature probe — the fallback sorts on this thread and says so
82
+ typeof Worker === 'undefined' ||
81
83
  typeof Blob === 'undefined' ||
82
84
  typeof URL === 'undefined') {
83
85
  return fallback('this runtime has no Worker');
84
86
  }
85
87
  const url = URL.createObjectURL(new Blob([workerSource(sortSource)], { type: 'text/javascript' }));
88
+ // platform: browser default — a blob worker, guarded by the probe above
86
89
  worker = new Worker(url);
87
90
  /* The blob is referenced by the live worker; the URL is only the handle. */
88
91
  URL.revokeObjectURL(url);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftengine/splats",
3
- "version": "3.63.0",
3
+ "version": "4.0.0",
4
4
  "description": "Gaussian splat captures: readers, an off-frame sort, and a pass that composes into the scene",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  ],
28
28
  "sideEffects": false,
29
29
  "peerDependencies": {
30
- "@driftengine/core": "3.63.0"
30
+ "@driftengine/core": "4.0.0"
31
31
  },
32
32
  "author": "Drift Technologies",
33
33
  "repository": {
@@ -1,4 +1,6 @@
1
- /** The two matrix operations this package needs, written once so no two callers disagree. */
1
+ /** The matrix operations this package needs, written once so no two callers disagree. */
2
+
3
+ import { jitterClip } from '@driftengine/core';
2
4
 
3
5
  /**
4
6
  * `out = a * b`, column-major, sixteen multiply-adds and no dependency.
@@ -79,3 +81,25 @@ export function cameraInCaptureSpace(
79
81
 
80
82
  /** Scratch for the product above, so a frame allocates nothing. */
81
83
  const PRODUCT = new Float32Array(16);
84
+
85
+ /**
86
+ * The projection a frame draws a capture with: the caller's, moved by the frame's jitter, then
87
+ * corrected for the backend. `scratch` holds sixteen floats and is overwritten.
88
+ *
89
+ * **In that order, because the jitter is stated in the camera's own convention** — see
90
+ * `PrepareContext.jitter`. A reconstructed frame un-jitters every sample it takes, so a capture
91
+ * drawn without the offset is placed up to half a render pixel from where the resolve looks for it,
92
+ * differently each frame; and the correction negates y, so the same offset applied after it lands
93
+ * on the other side of the pixel. A frame that is not reconstructed hands a zero jitter, and the
94
+ * result is the corrected projection it always was.
95
+ */
96
+ export function projectionForFrame(
97
+ out: Float32Array,
98
+ correction: ArrayLike<number>,
99
+ projection: ArrayLike<number>,
100
+ jitter: ArrayLike<number>,
101
+ scratch: Float32Array,
102
+ ): void {
103
+ jitterClip(scratch, projection, jitter);
104
+ multiplyMat4(out, correction, scratch);
105
+ }
package/src/splatPass.ts CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { SPLAT_BINDINGS } from './shaders/generated/splat.wgsl.ts';
4
4
  import { SPLAT_STRIDE, splatRows, splatTexels } from './splatLayout.ts';
5
- import { cameraInCaptureSpace, multiplyMat4 } from './splatMatrix.ts';
5
+ import { cameraInCaptureSpace, multiplyMat4, projectionForFrame } from './splatMatrix.ts';
6
6
  import { splatBoundsVisible } from './splatCull.ts';
7
7
  import {
8
8
  createWebgl2Splats,
@@ -125,6 +125,12 @@ export function createSplatPass(splats: SplatData, label = 'splats'): SplatPass
125
125
  const view = new Float32Array(16);
126
126
  /** The caller's projection, pre-multiplied by the backend's clip correction. */
127
127
  const projection = new Float32Array(16);
128
+ /**
129
+ * The caller's projection as given, kept so a WebGPU frame can move it by the frame's jitter
130
+ * before correcting it. See `projectionForFrame`.
131
+ */
132
+ const callerProjection = new Float32Array(IDENTITY);
133
+ const jitterScratch = new Float32Array(16);
128
134
  /**
129
135
  * Where the camera is in the capture's own space, which is where the harmonics were trained.
130
136
  *
@@ -186,6 +192,7 @@ export function createSplatPass(splats: SplatData, label = 'splats'): SplatPass
186
192
 
187
193
  setView(next: SplatView): void {
188
194
  view.set(next.view);
195
+ callerProjection.set(next.projection);
189
196
  multiplyMat4(projection, clipCorrection, next.projection);
190
197
  viewportX = next.widthPx;
191
198
  viewportY = next.heightPx;
@@ -291,6 +298,8 @@ export function createSplatPass(splats: SplatData, label = 'splats'): SplatPass
291
298
  uploadGpuOrder(device, gpu, pendingOrder, paddedOrder);
292
299
  pendingOrder = null;
293
300
  }
301
+ /* The frame's jitter, which is zero unless the frame is reconstructed. */
302
+ projectionForFrame(projection, clipCorrection, callerProjection, ctx.jitter, jitterScratch);
294
303
  writeVertexUniforms(gpu);
295
304
  device.queue.writeBuffer(gpu.vertexUniforms, 0, gpu.vertexScratch);
296
305
  gpu.fragmentInts[FRAG.fields.uOutputTransform.offset / 4] = ctx.outputTransform;
@@ -86,6 +86,7 @@ export function createSplatSortWorker(sortSource: string): SplatSortFn {
86
86
 
87
87
  try {
88
88
  if (
89
+ // platform: feature probe — the fallback sorts on this thread and says so
89
90
  typeof Worker === 'undefined' ||
90
91
  typeof Blob === 'undefined' ||
91
92
  typeof URL === 'undefined'
@@ -95,6 +96,7 @@ export function createSplatSortWorker(sortSource: string): SplatSortFn {
95
96
  const url = URL.createObjectURL(
96
97
  new Blob([workerSource(sortSource)], { type: 'text/javascript' }),
97
98
  );
99
+ // platform: browser default — a blob worker, guarded by the probe above
98
100
  worker = new Worker(url);
99
101
  /* The blob is referenced by the live worker; the URL is only the handle. */
100
102
  URL.revokeObjectURL(url);