@driftengine/capture 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/LICENSE +202 -0
- package/NOTICE +29 -0
- package/README.md +68 -0
- package/dist/browserFrames.d.ts +31 -0
- package/dist/browserFrames.js +105 -0
- package/dist/collide.d.ts +59 -0
- package/dist/collide.js +92 -0
- package/dist/decimate.d.ts +42 -0
- package/dist/decimate.js +439 -0
- package/dist/delight.d.ts +42 -0
- package/dist/delight.js +351 -0
- package/dist/delightScene.d.ts +37 -0
- package/dist/delightScene.js +183 -0
- package/dist/depth.d.ts +44 -0
- package/dist/depth.js +53 -0
- package/dist/features.d.ts +33 -0
- package/dist/features.js +260 -0
- package/dist/frames.d.ts +47 -0
- package/dist/frames.js +87 -0
- package/dist/fusion.d.ts +77 -0
- package/dist/fusion.js +152 -0
- package/dist/gaussians/adam.d.ts +57 -0
- package/dist/gaussians/adam.js +88 -0
- package/dist/gaussians/depth.d.ts +8 -0
- package/dist/gaussians/depth.js +73 -0
- package/dist/gaussians/gradients.d.ts +36 -0
- package/dist/gaussians/gradients.js +228 -0
- package/dist/gaussians/harmonics.d.ts +46 -0
- package/dist/gaussians/harmonics.js +99 -0
- package/dist/gaussians/optimise.d.ts +50 -0
- package/dist/gaussians/optimise.js +125 -0
- package/dist/gaussians/parameters.d.ts +35 -0
- package/dist/gaussians/parameters.js +165 -0
- package/dist/gaussians/project.d.ts +72 -0
- package/dist/gaussians/project.js +137 -0
- package/dist/gaussians/projectGradient.d.ts +15 -0
- package/dist/gaussians/projectGradient.js +213 -0
- package/dist/gaussians/rasterise.d.ts +13 -0
- package/dist/gaussians/rasterise.js +65 -0
- package/dist/gaussians/refine.d.ts +36 -0
- package/dist/gaussians/refine.js +136 -0
- package/dist/gaussians/similarity.d.ts +25 -0
- package/dist/gaussians/similarity.js +80 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +96 -0
- package/dist/marching.d.ts +38 -0
- package/dist/marching.js +258 -0
- package/dist/masks.d.ts +59 -0
- package/dist/masks.js +165 -0
- package/dist/math/dense.d.ts +68 -0
- package/dist/math/dense.js +294 -0
- package/dist/math/schur.d.ts +23 -0
- package/dist/math/schur.js +140 -0
- package/dist/models/batchNorm.d.ts +17 -0
- package/dist/models/batchNorm.js +24 -0
- package/dist/models/clipTokenizer.d.ts +40 -0
- package/dist/models/clipTokenizer.js +122 -0
- package/dist/models/clipTower.d.ts +27 -0
- package/dist/models/clipTower.js +45 -0
- package/dist/models/depthAnything2.d.ts +38 -0
- package/dist/models/depthAnything2.js +163 -0
- package/dist/models/depthAnything3.d.ts +26 -0
- package/dist/models/depthAnything3.js +171 -0
- package/dist/models/dualDpt.d.ts +19 -0
- package/dist/models/dualDpt.js +138 -0
- package/dist/models/hiera.d.ts +49 -0
- package/dist/models/hiera.js +120 -0
- package/dist/models/hieraAttention.d.ts +16 -0
- package/dist/models/hieraAttention.js +68 -0
- package/dist/models/miniature.d.ts +36 -0
- package/dist/models/miniature.js +216 -0
- package/dist/models/mobileSam.d.ts +41 -0
- package/dist/models/mobileSam.js +88 -0
- package/dist/models/owlv2.d.ts +49 -0
- package/dist/models/owlv2.js +131 -0
- package/dist/models/owlv2Detect.d.ts +25 -0
- package/dist/models/owlv2Detect.js +80 -0
- package/dist/models/owlv2Miniature.d.ts +27 -0
- package/dist/models/owlv2Miniature.js +87 -0
- package/dist/models/rope.d.ts +23 -0
- package/dist/models/rope.js +84 -0
- package/dist/models/sam21.d.ts +45 -0
- package/dist/models/sam21.js +126 -0
- package/dist/models/sam21Miniature.d.ts +37 -0
- package/dist/models/sam21Miniature.js +194 -0
- package/dist/models/sam21Video.d.ts +30 -0
- package/dist/models/sam21Video.js +101 -0
- package/dist/models/sam2Choice.d.ts +19 -0
- package/dist/models/sam2Choice.js +55 -0
- package/dist/models/sam2Decoder.d.ts +36 -0
- package/dist/models/sam2Decoder.js +108 -0
- package/dist/models/sam2Memories.d.ts +54 -0
- package/dist/models/sam2Memories.js +93 -0
- package/dist/models/sam2Memory.d.ts +30 -0
- package/dist/models/sam2Memory.js +136 -0
- package/dist/models/sam2Positions.d.ts +24 -0
- package/dist/models/sam2Positions.js +94 -0
- package/dist/models/sam2Tracker.d.ts +69 -0
- package/dist/models/sam2Tracker.js +172 -0
- package/dist/models/samDecoder.d.ts +45 -0
- package/dist/models/samDecoder.js +96 -0
- package/dist/models/samMiniature.d.ts +43 -0
- package/dist/models/samMiniature.js +161 -0
- package/dist/models/samPrompt.d.ts +57 -0
- package/dist/models/samPrompt.js +160 -0
- package/dist/models/seeded.d.ts +16 -0
- package/dist/models/seeded.js +48 -0
- package/dist/models/tinyVit.d.ts +38 -0
- package/dist/models/tinyVit.js +82 -0
- package/dist/models/twoWay.d.ts +46 -0
- package/dist/models/twoWay.js +67 -0
- package/dist/models/vit.d.ts +45 -0
- package/dist/models/vit.js +157 -0
- package/dist/models/windowAttention.d.ts +25 -0
- package/dist/models/windowAttention.js +104 -0
- package/dist/poses.d.ts +31 -0
- package/dist/poses.js +750 -0
- package/dist/prepare.d.ts +30 -0
- package/dist/prepare.js +238 -0
- package/dist/propose.d.ts +36 -0
- package/dist/propose.js +63 -0
- package/dist/resample.d.ts +39 -0
- package/dist/resample.js +165 -0
- package/dist/run.d.ts +3 -0
- package/dist/run.js +1 -0
- package/dist/scene.d.ts +104 -0
- package/dist/scene.js +109 -0
- package/dist/segment.d.ts +40 -0
- package/dist/segment.js +184 -0
- package/dist/testScene.d.ts +55 -0
- package/dist/testScene.js +214 -0
- package/dist/twoView.d.ts +22 -0
- package/dist/twoView.js +435 -0
- package/package.json +63 -0
- package/src/browserFrames.ts +145 -0
- package/src/collide.ts +128 -0
- package/src/decimate.ts +510 -0
- package/src/delight.ts +427 -0
- package/src/delightScene.ts +249 -0
- package/src/depth.ts +100 -0
- package/src/features.ts +302 -0
- package/src/frames.ts +111 -0
- package/src/fusion.ts +229 -0
- package/src/gaussians/adam.ts +131 -0
- package/src/gaussians/depth.ts +87 -0
- package/src/gaussians/gradients.ts +285 -0
- package/src/gaussians/harmonics.ts +122 -0
- package/src/gaussians/optimise.ts +191 -0
- package/src/gaussians/parameters.ts +186 -0
- package/src/gaussians/project.ts +207 -0
- package/src/gaussians/projectGradient.ts +234 -0
- package/src/gaussians/rasterise.ts +81 -0
- package/src/gaussians/refine.ts +176 -0
- package/src/gaussians/similarity.ts +87 -0
- package/src/index.ts +225 -0
- package/src/marching.ts +358 -0
- package/src/masks.ts +201 -0
- package/src/math/dense.ts +317 -0
- package/src/math/schur.ts +167 -0
- package/src/models/batchNorm.ts +57 -0
- package/src/models/clipTokenizer.ts +129 -0
- package/src/models/clipTower.ts +87 -0
- package/src/models/depthAnything2.ts +276 -0
- package/src/models/depthAnything3.ts +229 -0
- package/src/models/dualDpt.ts +233 -0
- package/src/models/hiera.ts +231 -0
- package/src/models/hieraAttention.ts +121 -0
- package/src/models/miniature.ts +241 -0
- package/src/models/mobileSam.ts +135 -0
- package/src/models/owlv2.ts +190 -0
- package/src/models/owlv2Detect.ts +116 -0
- package/src/models/owlv2Miniature.ts +108 -0
- package/src/models/rope.ts +111 -0
- package/src/models/sam21.ts +182 -0
- package/src/models/sam21Miniature.ts +225 -0
- package/src/models/sam21Video.ts +154 -0
- package/src/models/sam2Choice.ts +62 -0
- package/src/models/sam2Decoder.ts +218 -0
- package/src/models/sam2Memories.ts +165 -0
- package/src/models/sam2Memory.ts +247 -0
- package/src/models/sam2Positions.ts +106 -0
- package/src/models/sam2Tracker.ts +300 -0
- package/src/models/samDecoder.ts +225 -0
- package/src/models/samMiniature.ts +190 -0
- package/src/models/samPrompt.ts +227 -0
- package/src/models/seeded.ts +50 -0
- package/src/models/tinyVit.ts +178 -0
- package/src/models/twoWay.ts +122 -0
- package/src/models/vit.ts +265 -0
- package/src/models/windowAttention.ts +155 -0
- package/src/poses.ts +894 -0
- package/src/prepare.ts +306 -0
- package/src/propose.ts +96 -0
- package/src/resample.ts +197 -0
- package/src/run.ts +7 -0
- package/src/scene.ts +141 -0
- package/src/segment.ts +220 -0
- package/src/testScene.ts +283 -0
- package/src/twoView.ts +536 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The screen-space gradients of one Gaussian carried back to its own parameters.
|
|
3
|
+
*
|
|
4
|
+
* **`project.ts` run backwards**, term for term: the perspective Jacobian, the covariance it acts
|
|
5
|
+
* on, the rotation and scale that covariance is built from, and the quaternion behind the rotation.
|
|
6
|
+
* Nothing here knows about pixels or compositing — it takes what `gradients.ts` gathered over a
|
|
7
|
+
* Gaussian's own pixels and answers what that means for where it stands and how it is shaped.
|
|
8
|
+
*/
|
|
9
|
+
import { rotationFromQuaternion } from './project.js';
|
|
10
|
+
/**
|
|
11
|
+
* The screen-space gradients carried back to a Gaussian's position, scale and rotation, by the
|
|
12
|
+
* same chain the projection is built from.
|
|
13
|
+
*/
|
|
14
|
+
export function screenToParameters(set, at, camera, dMeanX, dMeanY, dA, dB, dD, out) {
|
|
15
|
+
const [fx, fy] = camera.intrinsics;
|
|
16
|
+
const m = camera.worldToCamera;
|
|
17
|
+
const view = [0, 0, 0];
|
|
18
|
+
for (let r = 0; r < 3; r += 1) {
|
|
19
|
+
view[r] =
|
|
20
|
+
m[r * 4] * set.positions[at * 3] +
|
|
21
|
+
m[r * 4 + 1] * set.positions[at * 3 + 1] +
|
|
22
|
+
m[r * 4 + 2] * set.positions[at * 3 + 2] +
|
|
23
|
+
m[r * 4 + 3];
|
|
24
|
+
}
|
|
25
|
+
const z = view[2];
|
|
26
|
+
const invZ = 1 / z;
|
|
27
|
+
const invZ2 = invZ * invZ;
|
|
28
|
+
const invZ3 = invZ2 * invZ;
|
|
29
|
+
/* The world covariance in the camera's frame, which the Jacobian acts on. */
|
|
30
|
+
const rotation = new Float64Array(9);
|
|
31
|
+
rotationFromQuaternion(set.rotations, at, rotation);
|
|
32
|
+
const scaled = new Float64Array(9);
|
|
33
|
+
for (let r = 0; r < 3; r += 1) {
|
|
34
|
+
for (let c = 0; c < 3; c += 1) {
|
|
35
|
+
scaled[r * 3 + c] = rotation[r * 3 + c] * set.scales[at * 3 + c];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const world = new Float64Array(9);
|
|
39
|
+
for (let r = 0; r < 3; r += 1) {
|
|
40
|
+
for (let c = 0; c < 3; c += 1) {
|
|
41
|
+
let sum = 0;
|
|
42
|
+
for (let k = 0; k < 3; k += 1)
|
|
43
|
+
sum += scaled[r * 3 + k] * scaled[c * 3 + k];
|
|
44
|
+
world[r * 3 + c] = sum;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const viewRotation = new Float64Array(9);
|
|
48
|
+
for (let r = 0; r < 3; r += 1) {
|
|
49
|
+
for (let c = 0; c < 3; c += 1)
|
|
50
|
+
viewRotation[r * 3 + c] = m[r * 4 + c];
|
|
51
|
+
}
|
|
52
|
+
const inCamera = new Float64Array(9);
|
|
53
|
+
for (let r = 0; r < 3; r += 1) {
|
|
54
|
+
for (let c = 0; c < 3; c += 1) {
|
|
55
|
+
let sum = 0;
|
|
56
|
+
for (let k = 0; k < 3; k += 1) {
|
|
57
|
+
for (let j = 0; j < 3; j += 1) {
|
|
58
|
+
sum +=
|
|
59
|
+
viewRotation[r * 3 + k] *
|
|
60
|
+
world[k * 3 + j] *
|
|
61
|
+
viewRotation[c * 3 + j];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
inCamera[r * 3 + c] = sum;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const j = [
|
|
68
|
+
[fx * invZ, 0, -fx * view[0] * invZ2],
|
|
69
|
+
[0, fy * invZ, -fy * view[1] * invZ2],
|
|
70
|
+
];
|
|
71
|
+
/* dΣ'/dΣcam, then the same into the world and the Gaussian's own axes. */
|
|
72
|
+
const dScreen = [
|
|
73
|
+
[dA, dB / 2],
|
|
74
|
+
[dB / 2, dD],
|
|
75
|
+
];
|
|
76
|
+
const dCamera = new Float64Array(9);
|
|
77
|
+
for (let k = 0; k < 3; k += 1) {
|
|
78
|
+
for (let l = 0; l < 3; l += 1) {
|
|
79
|
+
let sum = 0;
|
|
80
|
+
for (let r = 0; r < 2; r += 1) {
|
|
81
|
+
for (let c = 0; c < 2; c += 1) {
|
|
82
|
+
sum +=
|
|
83
|
+
dScreen[r][c] *
|
|
84
|
+
j[r][k] *
|
|
85
|
+
j[c][l];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
dCamera[k * 3 + l] = sum;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const dWorld = new Float64Array(9);
|
|
92
|
+
for (let k = 0; k < 3; k += 1) {
|
|
93
|
+
for (let l = 0; l < 3; l += 1) {
|
|
94
|
+
let sum = 0;
|
|
95
|
+
for (let r = 0; r < 3; r += 1) {
|
|
96
|
+
for (let c = 0; c < 3; c += 1) {
|
|
97
|
+
sum +=
|
|
98
|
+
viewRotation[r * 3 + k] *
|
|
99
|
+
dCamera[r * 3 + c] *
|
|
100
|
+
viewRotation[c * 3 + l];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
dWorld[k * 3 + l] = sum;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/* Σ = M · Mᵀ with M = R · diag(s), so dΣ/dM = 2 · dΣ · M for a symmetric dΣ. */
|
|
107
|
+
const dM = new Float64Array(9);
|
|
108
|
+
for (let r = 0; r < 3; r += 1) {
|
|
109
|
+
for (let c = 0; c < 3; c += 1) {
|
|
110
|
+
let sum = 0;
|
|
111
|
+
for (let k = 0; k < 3; k += 1)
|
|
112
|
+
sum += dWorld[r * 3 + k] * scaled[k * 3 + c];
|
|
113
|
+
dM[r * 3 + c] = 2 * sum;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (let c = 0; c < 3; c += 1) {
|
|
117
|
+
let sum = 0;
|
|
118
|
+
for (let r = 0; r < 3; r += 1)
|
|
119
|
+
sum += dM[r * 3 + c] * rotation[r * 3 + c];
|
|
120
|
+
out.scales[at * 3 + c] = out.scales[at * 3 + c] + sum;
|
|
121
|
+
}
|
|
122
|
+
/* And through the rotation to the quaternion, by its own derivatives. */
|
|
123
|
+
const dRotation = new Float64Array(9);
|
|
124
|
+
for (let r = 0; r < 3; r += 1) {
|
|
125
|
+
for (let c = 0; c < 3; c += 1) {
|
|
126
|
+
dRotation[r * 3 + c] = dM[r * 3 + c] * set.scales[at * 3 + c];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
quaternionGradient(set.rotations, at, dRotation, out.rotations);
|
|
130
|
+
/*
|
|
131
|
+
* The position moves the mean and, through the Jacobian, the covariance. The second term is what
|
|
132
|
+
* a gradient written from the mean alone misses — it is small at the centre of the frame and is
|
|
133
|
+
* not small at its edges.
|
|
134
|
+
*/
|
|
135
|
+
const dView = [
|
|
136
|
+
dMeanX * fx * invZ,
|
|
137
|
+
dMeanY * fy * invZ,
|
|
138
|
+
-dMeanX * fx * view[0] * invZ2 - dMeanY * fy * view[1] * invZ2,
|
|
139
|
+
];
|
|
140
|
+
/* dJ/dview, the only part of Σ' that the position enters. */
|
|
141
|
+
const dJ = [
|
|
142
|
+
[
|
|
143
|
+
[0, 0, -fx * invZ2],
|
|
144
|
+
[0, 0, 0],
|
|
145
|
+
],
|
|
146
|
+
[
|
|
147
|
+
[0, 0, 0],
|
|
148
|
+
[0, 0, -fy * invZ2],
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
[-fx * invZ2, 0, 2 * fx * view[0] * invZ3],
|
|
152
|
+
[0, -fy * invZ2, 2 * fy * view[1] * invZ3],
|
|
153
|
+
],
|
|
154
|
+
];
|
|
155
|
+
for (let k = 0; k < 3; k += 1) {
|
|
156
|
+
for (let l = 0; l < 3; l += 1) {
|
|
157
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
158
|
+
let sum = 0;
|
|
159
|
+
for (let r = 0; r < 2; r += 1) {
|
|
160
|
+
for (let c = 0; c < 2; c += 1) {
|
|
161
|
+
const term = dJ[axis][r][k] *
|
|
162
|
+
j[c][l] +
|
|
163
|
+
j[r][k] *
|
|
164
|
+
dJ[axis][c][l];
|
|
165
|
+
sum += dScreen[r][c] * term * inCamera[k * 3 + l];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
dView[axis] = dView[axis] + sum;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
173
|
+
let sum = 0;
|
|
174
|
+
for (let r = 0; r < 3; r += 1)
|
|
175
|
+
sum += dView[r] * m[r * 4 + axis];
|
|
176
|
+
out.positions[at * 3 + axis] = out.positions[at * 3 + axis] + sum;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** A rotation matrix's gradient carried back to the quaternion it came from. */
|
|
180
|
+
function quaternionGradient(q, at, dRotation, out) {
|
|
181
|
+
const x = q[at * 4];
|
|
182
|
+
const y = q[at * 4 + 1];
|
|
183
|
+
const z = q[at * 4 + 2];
|
|
184
|
+
const w = q[at * 4 + 3];
|
|
185
|
+
const length = Math.sqrt(x * x + y * y + z * z + w * w) || 1;
|
|
186
|
+
const nx = x / length;
|
|
187
|
+
const ny = y / length;
|
|
188
|
+
const nz = z / length;
|
|
189
|
+
const nw = w / length;
|
|
190
|
+
/* dR/d(normalised quaternion), the nine entries against each of the four. */
|
|
191
|
+
const d = [
|
|
192
|
+
[0, 2 * ny, 2 * nz, 2 * ny, -4 * nx, -2 * nw, 2 * nz, 2 * nw, -4 * nx],
|
|
193
|
+
[-4 * ny, 2 * nx, 2 * nw, 2 * nx, 0, 2 * nz, -2 * nw, 2 * nz, -4 * ny],
|
|
194
|
+
[-4 * nz, -2 * nw, 2 * nx, 2 * nw, -4 * nz, 2 * ny, 2 * nx, 2 * ny, 0],
|
|
195
|
+
[0, -2 * nz, 2 * ny, 2 * nz, 0, -2 * nx, -2 * ny, 2 * nx, 0],
|
|
196
|
+
];
|
|
197
|
+
/* Through the normalisation: the part of a change that lengthens the quaternion does nothing. */
|
|
198
|
+
const raw = [0, 0, 0, 0];
|
|
199
|
+
for (let p = 0; p < 4; p += 1) {
|
|
200
|
+
let sum = 0;
|
|
201
|
+
for (let i = 0; i < 9; i += 1)
|
|
202
|
+
sum += d[p][i] * dRotation[i];
|
|
203
|
+
raw[p] = sum;
|
|
204
|
+
}
|
|
205
|
+
const unit = [nx, ny, nz, nw];
|
|
206
|
+
let along = 0;
|
|
207
|
+
for (let p = 0; p < 4; p += 1)
|
|
208
|
+
along += raw[p] * unit[p];
|
|
209
|
+
for (let p = 0; p < 4; p += 1) {
|
|
210
|
+
out[at * 4 + p] =
|
|
211
|
+
out[at * 4 + p] + (raw[p] - along * unit[p]) / length;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type GaussianSet, type RasterCamera } from './project.ts';
|
|
2
|
+
/**
|
|
3
|
+
* A frame this rasteriser writes: **single precision where it stands in for a render target, double
|
|
4
|
+
* where the caller is differentiating through it.** The fitting loop accumulates its loss in double
|
|
5
|
+
* and a frame quantised to single is a floor under every difference taken through one — measured at
|
|
6
|
+
* about a part in five hundred of the slope, against a part in ten million when the frame is double.
|
|
7
|
+
*/
|
|
8
|
+
export type Frame = Float32Array | Float64Array;
|
|
9
|
+
/**
|
|
10
|
+
* `set` through `camera` into `out`, four floats a pixel: linear colour, premultiplied as it
|
|
11
|
+
* composites, and the alpha the cloud covered the pixel with.
|
|
12
|
+
*/
|
|
13
|
+
export declare function rasteriseGaussians(set: GaussianSet, camera: RasterCamera, out: Frame): void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cloud of Gaussians drawn on the CPU: `project.ts`'s ellipses, composited nearest first.
|
|
3
|
+
*
|
|
4
|
+
* **It exists to be differentiated**, which is what separates it from the shader that draws splats
|
|
5
|
+
* in a game. A reference rasteriser is how the gradients in `gradients.ts` are checked — a central
|
|
6
|
+
* difference of this against the analytic answer — and how a fitted cloud is compared with the
|
|
7
|
+
* frames it was fitted to. It is thousands of times slower than the shader, and per-frame rules
|
|
8
|
+
* about allocation do not reach it.
|
|
9
|
+
*/
|
|
10
|
+
import { exactExp } from '@driftengine/core';
|
|
11
|
+
import { cameraCentre, sh1Basis, splatColour, viewDirection } from './harmonics.js';
|
|
12
|
+
import { visibleGaussians, } from './project.js';
|
|
13
|
+
/** Below this the Gaussian contributes nothing worth compositing. */
|
|
14
|
+
const FAINT = 1 / 255 / 4;
|
|
15
|
+
/**
|
|
16
|
+
* `set` through `camera` into `out`, four floats a pixel: linear colour, premultiplied as it
|
|
17
|
+
* composites, and the alpha the cloud covered the pixel with.
|
|
18
|
+
*/
|
|
19
|
+
export function rasteriseGaussians(set, camera, out) {
|
|
20
|
+
const { width, height } = camera;
|
|
21
|
+
out.fill(0);
|
|
22
|
+
const transmittance = new Float64Array(width * height).fill(1);
|
|
23
|
+
const { order, projected } = visibleGaussians(set, camera);
|
|
24
|
+
/* A Gaussian's colour is a function of where it is seen from, so it is resolved once a frame. */
|
|
25
|
+
const eye = new Float64Array(3);
|
|
26
|
+
cameraCentre(camera.worldToCamera, eye);
|
|
27
|
+
const direction = new Float64Array(3);
|
|
28
|
+
const basis = new Float64Array(3);
|
|
29
|
+
const colour = new Float64Array(3);
|
|
30
|
+
for (const at of order) {
|
|
31
|
+
const one = projected[at];
|
|
32
|
+
viewDirection(set.positions, at, eye, direction);
|
|
33
|
+
sh1Basis(direction, basis);
|
|
34
|
+
splatColour(set.colors, set.sh1, at, basis, colour);
|
|
35
|
+
const determinant = one.a * one.d - one.b * one.b;
|
|
36
|
+
const inverse = [one.d / determinant, -one.b / determinant, one.a / determinant];
|
|
37
|
+
const left = Math.max(0, Math.floor(one.x - one.radius));
|
|
38
|
+
const right = Math.min(width - 1, Math.ceil(one.x + one.radius));
|
|
39
|
+
const top = Math.max(0, Math.floor(one.y - one.radius));
|
|
40
|
+
const bottom = Math.min(height - 1, Math.ceil(one.y + one.radius));
|
|
41
|
+
const opacity = set.opacities[at];
|
|
42
|
+
for (let y = top; y <= bottom; y += 1) {
|
|
43
|
+
for (let x = left; x <= right; x += 1) {
|
|
44
|
+
const dx = x + 0.5 - one.x;
|
|
45
|
+
const dy = y + 0.5 - one.y;
|
|
46
|
+
const power = -0.5 *
|
|
47
|
+
(inverse[0] * dx * dx +
|
|
48
|
+
2 * inverse[1] * dx * dy +
|
|
49
|
+
inverse[2] * dy * dy);
|
|
50
|
+
/* The inverse of a positive definite covariance is positive definite, so `power` is at
|
|
51
|
+
most zero and the exponential at most one: there is nothing to guard against here. */
|
|
52
|
+
const alpha = opacity * exactExp(power);
|
|
53
|
+
if (alpha < FAINT)
|
|
54
|
+
continue;
|
|
55
|
+
const pixel = y * width + x;
|
|
56
|
+
const share = alpha * transmittance[pixel];
|
|
57
|
+
for (let c = 0; c < 3; c += 1) {
|
|
58
|
+
out[pixel * 4 + c] = out[pixel * 4 + c] + share * colour[c];
|
|
59
|
+
}
|
|
60
|
+
out[pixel * 4 + 3] = out[pixel * 4 + 3] + share;
|
|
61
|
+
transmittance[pixel] = transmittance[pixel] * (1 - alpha);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Family } from './adam.ts';
|
|
2
|
+
/** Below this a Gaussian contributes less than a quarter of one value of an eight-bit channel. */
|
|
3
|
+
export declare const FADED: number;
|
|
4
|
+
export interface RefineOptions {
|
|
5
|
+
/** The most Gaussians the cloud may hold. */
|
|
6
|
+
readonly budget: number;
|
|
7
|
+
/** A Gaussian wider than this is split rather than cloned, in metres. */
|
|
8
|
+
readonly splitAbove: number;
|
|
9
|
+
readonly random: () => number;
|
|
10
|
+
}
|
|
11
|
+
/** Everything the plan rewrites, in one place. */
|
|
12
|
+
export interface RefineCloud {
|
|
13
|
+
count: number;
|
|
14
|
+
readonly families: readonly Family[];
|
|
15
|
+
readonly positions: Family;
|
|
16
|
+
readonly logScales: Family;
|
|
17
|
+
readonly rotations: Family;
|
|
18
|
+
readonly opacities: Float64Array;
|
|
19
|
+
/** How hard the frames have pulled on each Gaussian since the last refinement. */
|
|
20
|
+
readonly motion: Float64Array;
|
|
21
|
+
readonly sources: Int32Array;
|
|
22
|
+
/** Where each of today's Gaussians ended up after a prune, so a split can find its own half. */
|
|
23
|
+
readonly reverse: Int32Array;
|
|
24
|
+
readonly fresh: Uint8Array;
|
|
25
|
+
readonly scratch: Float64Array;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Prune, then grow, then rewrite every family; answers the new count.
|
|
29
|
+
*
|
|
30
|
+
* The cloud's own arrays are rewritten in place through its scratch, so nothing is reallocated and
|
|
31
|
+
* the caller's view of it stays valid. **The materialised scales and opacities are not**: a slot
|
|
32
|
+
* that has just been split holds the logarithm it was given and the metres of whatever stood there
|
|
33
|
+
* before, so the caller materialises again before drawing. The fit does that at the top of every
|
|
34
|
+
* step, which is why this does not do it twice.
|
|
35
|
+
*/
|
|
36
|
+
export declare function refineCloud(cloud: RefineCloud, options: RefineOptions): number;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the cloud grows and where it is thrown away: densification and pruning, as one plan.
|
|
3
|
+
*
|
|
4
|
+
* **A fit that only moves the Gaussians it started with cannot see detail its seeds did not.** The
|
|
5
|
+
* seeds come from a sparse reconstruction — a few hundred corners — and a room has more surface
|
|
6
|
+
* than that. So the busiest Gaussians are made into two: a small one is **cloned**, which puts a
|
|
7
|
+
* second Gaussian where one was not enough, and a large one is **split**, which replaces a blur
|
|
8
|
+
* covering several surfaces with two that can each take one. The reference implementation decides
|
|
9
|
+
* between the two by the same test.
|
|
10
|
+
*
|
|
11
|
+
* **Busiest by rank rather than by a threshold.** The reference compares an accumulated screen-space
|
|
12
|
+
* gradient against 0.0002, a number in units of that implementation's own normalisation, and a
|
|
13
|
+
* number in units nobody can check is a number nobody can tune. This takes a fixed fraction of the
|
|
14
|
+
* cloud — the most-moved fifth — which says the same thing scale-free: *these are the Gaussians the
|
|
15
|
+
* frames keep pulling on.* What it gives up is the reference's property that a converged fit stops
|
|
16
|
+
* growing on its own; the budget is what stops it here, and the pruning below is what keeps the
|
|
17
|
+
* budget spent on Gaussians that are doing something.
|
|
18
|
+
*
|
|
19
|
+
* **Pruning is by opacity, and it happens first.** A Gaussian the fit has faded to nothing is
|
|
20
|
+
* carrying a slot and contributing less light than a pixel can hold.
|
|
21
|
+
*/
|
|
22
|
+
import { exactExp, exactLog } from '@driftengine/core';
|
|
23
|
+
import { gather } from './adam.js';
|
|
24
|
+
import { rotationFromQuaternion } from './project.js';
|
|
25
|
+
/** Below this a Gaussian contributes less than a quarter of one value of an eight-bit channel. */
|
|
26
|
+
export const FADED = 1 / 255 / 4;
|
|
27
|
+
/** How much of the cloud is made into two, where there is room. */
|
|
28
|
+
const BUSIEST = 0.2;
|
|
29
|
+
/** A split halves what it divides, near enough: the reference's own divisor. */
|
|
30
|
+
const SPLIT_SHRINK = 1.6;
|
|
31
|
+
/**
|
|
32
|
+
* Prune, then grow, then rewrite every family; answers the new count.
|
|
33
|
+
*
|
|
34
|
+
* The cloud's own arrays are rewritten in place through its scratch, so nothing is reallocated and
|
|
35
|
+
* the caller's view of it stays valid. **The materialised scales and opacities are not**: a slot
|
|
36
|
+
* that has just been split holds the logarithm it was given and the metres of whatever stood there
|
|
37
|
+
* before, so the caller materialises again before drawing. The fit does that at the top of every
|
|
38
|
+
* step, which is why this does not do it twice.
|
|
39
|
+
*/
|
|
40
|
+
export function refineCloud(cloud, options) {
|
|
41
|
+
const { budget, splitAbove, random } = options;
|
|
42
|
+
const { sources, fresh, motion } = cloud;
|
|
43
|
+
/* Everything still worth drawing, in order, with its history intact. */
|
|
44
|
+
const { reverse } = cloud;
|
|
45
|
+
let kept = 0;
|
|
46
|
+
for (let at = 0; at < cloud.count; at += 1) {
|
|
47
|
+
reverse[at] = -1;
|
|
48
|
+
if (cloud.opacities[at] < FADED)
|
|
49
|
+
continue;
|
|
50
|
+
reverse[at] = kept;
|
|
51
|
+
sources[kept] = at;
|
|
52
|
+
fresh[kept] = 0;
|
|
53
|
+
kept += 1;
|
|
54
|
+
}
|
|
55
|
+
/* A prune that emptied the cloud would leave the fit nothing to descend, so it is refused. */
|
|
56
|
+
if (kept === 0)
|
|
57
|
+
return cloud.count;
|
|
58
|
+
/* The busiest fifth of what is left, most-pulled first, as far as the budget allows. */
|
|
59
|
+
const order = Array.from({ length: kept }, (_, slot) => slot);
|
|
60
|
+
order.sort((a, b) => {
|
|
61
|
+
const first = motion[sources[a]];
|
|
62
|
+
const second = motion[sources[b]];
|
|
63
|
+
return first === second ? sources[a] - sources[b] : second - first;
|
|
64
|
+
});
|
|
65
|
+
/*
|
|
66
|
+
* At least one where there is room and something is pulling, because a fifth of a small cloud
|
|
67
|
+
* rounds to none — and a cloud of four that can never grow however long it is fitted is a cliff
|
|
68
|
+
* nobody would look for.
|
|
69
|
+
*/
|
|
70
|
+
const room = budget - kept;
|
|
71
|
+
const share = Math.floor(kept * BUSIEST);
|
|
72
|
+
const wanted = Math.min(room, share > 1 ? share : 1);
|
|
73
|
+
let total = kept;
|
|
74
|
+
const rotation = new Float64Array(9);
|
|
75
|
+
for (let taken = 0; taken < wanted; taken += 1) {
|
|
76
|
+
const slot = order[taken];
|
|
77
|
+
const from = sources[slot];
|
|
78
|
+
if (!(motion[from] > 0))
|
|
79
|
+
break;
|
|
80
|
+
sources[total] = from;
|
|
81
|
+
fresh[total] = 1;
|
|
82
|
+
total += 1;
|
|
83
|
+
}
|
|
84
|
+
gather(cloud.families, sources, fresh, total, cloud.scratch);
|
|
85
|
+
/*
|
|
86
|
+
* Now the two halves are told apart. A clone is left where it was — its own gradient is what
|
|
87
|
+
* separates it, and its Adam state is empty where its parent's is not. A split is shrunk and both
|
|
88
|
+
* halves are moved, because two Gaussians of the parent's size in the parent's place are one
|
|
89
|
+
* blur drawn twice.
|
|
90
|
+
*/
|
|
91
|
+
for (let slot = kept; slot < total; slot += 1) {
|
|
92
|
+
const parent = sources[slot];
|
|
93
|
+
let widest = 0;
|
|
94
|
+
for (let c = 0; c < 3; c += 1) {
|
|
95
|
+
const scale = exactExp(cloud.logScales.values[slot * 3 + c]);
|
|
96
|
+
widest = scale > widest ? scale : widest;
|
|
97
|
+
}
|
|
98
|
+
if (widest <= splitAbove)
|
|
99
|
+
continue;
|
|
100
|
+
rotationFromQuaternion(cloud.rotations.values, slot, rotation);
|
|
101
|
+
shrinkAndDisplace(cloud, slot, rotation, random);
|
|
102
|
+
shrinkAndDisplace(cloud, reverse[parent], rotation, random);
|
|
103
|
+
}
|
|
104
|
+
cloud.count = total;
|
|
105
|
+
motion.fill(0);
|
|
106
|
+
return total;
|
|
107
|
+
}
|
|
108
|
+
/** One half of a split: smaller than what it came from, and somewhere inside it. */
|
|
109
|
+
function shrinkAndDisplace(cloud, slot, rotation, random) {
|
|
110
|
+
if (slot < 0)
|
|
111
|
+
return;
|
|
112
|
+
displace(cloud, slot, rotation, random);
|
|
113
|
+
for (let c = 0; c < 3; c += 1) {
|
|
114
|
+
cloud.logScales.values[slot * 3 + c] =
|
|
115
|
+
cloud.logScales.values[slot * 3 + c] - exactLog(SPLIT_SHRINK);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** One sample from a Gaussian's own shape, added to where it stands. */
|
|
119
|
+
function displace(cloud, slot, rotation, random) {
|
|
120
|
+
/*
|
|
121
|
+
* A sum of three uniforms either side of zero rather than a normal deviate: the fit only needs
|
|
122
|
+
* two halves to land somewhere inside the shape they came from, and a Box–Muller transform would
|
|
123
|
+
* want a logarithm and a cosine for no gain the picture can show.
|
|
124
|
+
*/
|
|
125
|
+
const local = [0, 0, 0];
|
|
126
|
+
for (let c = 0; c < 3; c += 1) {
|
|
127
|
+
const spread = random() + random() + random() - 1.5;
|
|
128
|
+
local[c] = spread * exactExp(cloud.logScales.values[slot * 3 + c]);
|
|
129
|
+
}
|
|
130
|
+
for (let r = 0; r < 3; r += 1) {
|
|
131
|
+
let sum = 0;
|
|
132
|
+
for (let c = 0; c < 3; c += 1)
|
|
133
|
+
sum += rotation[r * 3 + c] * local[c];
|
|
134
|
+
cloud.positions.values[slot * 3 + r] = cloud.positions.values[slot * 3 + r] + sum;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How alike two pictures are, structure included.
|
|
3
|
+
*
|
|
4
|
+
* **A squared difference is not a likeness.** It is what the fit descends, because it has a
|
|
5
|
+
* gradient, and it is the wrong thing to judge the result by: a cloud that is uniformly a little
|
|
6
|
+
* dark scores badly on it and looks right, and one that is right on average with the structure
|
|
7
|
+
* smeared away scores well and looks wrong. Structural similarity compares local means, variances
|
|
8
|
+
* and covariance, so it answers the question a reader actually asks of a capture.
|
|
9
|
+
*
|
|
10
|
+
* **Uniform windows rather than a Gaussian one.** The original weights each window by an 11 × 11
|
|
11
|
+
* Gaussian; this walks 8 × 8 blocks with equal weight, which is the same three terms over a
|
|
12
|
+
* different support and is what every fast implementation does. What it gives up is comparability
|
|
13
|
+
* with a number quoted from the paper — so a floor here is measured against this implementation
|
|
14
|
+
* and not lifted from one written elsewhere.
|
|
15
|
+
*/
|
|
16
|
+
/** The side of the block the three statistics are taken over. */
|
|
17
|
+
export declare const SIMILARITY_WINDOW = 8;
|
|
18
|
+
/**
|
|
19
|
+
* The mean structural similarity of `a` and `b`, one value a pixel, over every window.
|
|
20
|
+
*
|
|
21
|
+
* A picture narrower or shorter than a window is compared as one window rather than refused: it
|
|
22
|
+
* has a mean and a variance like any other, and the alternative is a measure that cannot answer
|
|
23
|
+
* for a thumbnail.
|
|
24
|
+
*/
|
|
25
|
+
export declare function structuralSimilarity(a: ArrayLike<number>, b: ArrayLike<number>, width: number, height: number): number;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How alike two pictures are, structure included.
|
|
3
|
+
*
|
|
4
|
+
* **A squared difference is not a likeness.** It is what the fit descends, because it has a
|
|
5
|
+
* gradient, and it is the wrong thing to judge the result by: a cloud that is uniformly a little
|
|
6
|
+
* dark scores badly on it and looks right, and one that is right on average with the structure
|
|
7
|
+
* smeared away scores well and looks wrong. Structural similarity compares local means, variances
|
|
8
|
+
* and covariance, so it answers the question a reader actually asks of a capture.
|
|
9
|
+
*
|
|
10
|
+
* **Uniform windows rather than a Gaussian one.** The original weights each window by an 11 × 11
|
|
11
|
+
* Gaussian; this walks 8 × 8 blocks with equal weight, which is the same three terms over a
|
|
12
|
+
* different support and is what every fast implementation does. What it gives up is comparability
|
|
13
|
+
* with a number quoted from the paper — so a floor here is measured against this implementation
|
|
14
|
+
* and not lifted from one written elsewhere.
|
|
15
|
+
*/
|
|
16
|
+
/** The side of the block the three statistics are taken over. */
|
|
17
|
+
export const SIMILARITY_WINDOW = 8;
|
|
18
|
+
/*
|
|
19
|
+
* The stabilisers, for values on 0 to 1. They keep a window where both pictures are flat from
|
|
20
|
+
* dividing zero by zero, and they are the reason two dark patches are not declared identical on
|
|
21
|
+
* the strength of a rounding difference.
|
|
22
|
+
*/
|
|
23
|
+
const C1 = 0.01 * 0.01;
|
|
24
|
+
const C2 = 0.03 * 0.03;
|
|
25
|
+
/**
|
|
26
|
+
* The mean structural similarity of `a` and `b`, one value a pixel, over every window.
|
|
27
|
+
*
|
|
28
|
+
* A picture narrower or shorter than a window is compared as one window rather than refused: it
|
|
29
|
+
* has a mean and a variance like any other, and the alternative is a measure that cannot answer
|
|
30
|
+
* for a thumbnail.
|
|
31
|
+
*/
|
|
32
|
+
export function structuralSimilarity(a, b, width, height) {
|
|
33
|
+
let total = 0;
|
|
34
|
+
let windows = 0;
|
|
35
|
+
for (let top = 0; top < height; top += SIMILARITY_WINDOW) {
|
|
36
|
+
for (let left = 0; left < width; left += SIMILARITY_WINDOW) {
|
|
37
|
+
const right = Math.min(width, left + SIMILARITY_WINDOW);
|
|
38
|
+
const bottom = Math.min(height, top + SIMILARITY_WINDOW);
|
|
39
|
+
const pixels = (right - left) * (bottom - top);
|
|
40
|
+
if (pixels === 0)
|
|
41
|
+
continue;
|
|
42
|
+
let sumA = 0;
|
|
43
|
+
let sumB = 0;
|
|
44
|
+
for (let y = top; y < bottom; y += 1) {
|
|
45
|
+
for (let x = left; x < right; x += 1) {
|
|
46
|
+
sumA += a[y * width + x];
|
|
47
|
+
sumB += b[y * width + x];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const meanA = sumA / pixels;
|
|
51
|
+
const meanB = sumB / pixels;
|
|
52
|
+
let varianceA = 0;
|
|
53
|
+
let varianceB = 0;
|
|
54
|
+
let covariance = 0;
|
|
55
|
+
for (let y = top; y < bottom; y += 1) {
|
|
56
|
+
for (let x = left; x < right; x += 1) {
|
|
57
|
+
const offA = a[y * width + x] - meanA;
|
|
58
|
+
const offB = b[y * width + x] - meanB;
|
|
59
|
+
varianceA += offA * offA;
|
|
60
|
+
varianceB += offB * offB;
|
|
61
|
+
covariance += offA * offB;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/*
|
|
65
|
+
* Divided by n − 1 where there is more than one pixel, which is the unbiased estimate the
|
|
66
|
+
* original uses. A single-pixel window has no spread to estimate and is compared by its
|
|
67
|
+
* means alone.
|
|
68
|
+
*/
|
|
69
|
+
const spread = pixels > 1 ? pixels - 1 : 1;
|
|
70
|
+
varianceA /= spread;
|
|
71
|
+
varianceB /= spread;
|
|
72
|
+
covariance /= spread;
|
|
73
|
+
const luminance = (2 * meanA * meanB + C1) / (meanA * meanA + meanB * meanB + C1);
|
|
74
|
+
const structure = (2 * covariance + C2) / (varianceA + varianceB + C2);
|
|
75
|
+
total += luminance * structure;
|
|
76
|
+
windows += 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return windows === 0 ? 1 : total / windows;
|
|
80
|
+
}
|