@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,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the fit actually steps, and how it becomes what the rasteriser draws.
|
|
3
|
+
*
|
|
4
|
+
* **Two of the six families are not the quantity they stand for.** A scale is held as its
|
|
5
|
+
* logarithm and an opacity as its logit, so that no step can carry either past the end of its own
|
|
6
|
+
* range — a negative standard deviation is not a covariance, and an opacity above one is light
|
|
7
|
+
* from a Gaussian that was never there. Everything downstream works in the real quantities, so
|
|
8
|
+
* this is where the two are made and where the gradients come back through them.
|
|
9
|
+
*
|
|
10
|
+
* **The chain back is one multiplication each and is nearly invisible to Adam**, which divides a
|
|
11
|
+
* step by the size of its own gradients and so cannot see a positive factor applied to every one
|
|
12
|
+
* of them. That is exactly why it is tested here by its own arithmetic rather than through a fit:
|
|
13
|
+
* a fit descends about as well with the factor missing, and the gradient is still wrong.
|
|
14
|
+
*/
|
|
15
|
+
import { exactExp, exactLog } from '@driftengine/core';
|
|
16
|
+
import type { SplatSource } from '@driftengine/splats';
|
|
17
|
+
|
|
18
|
+
import { createFamily, type Family } from './adam.ts';
|
|
19
|
+
import { SCREEN_GRADIENTS, type GaussianGradients } from './gradients.ts';
|
|
20
|
+
import { SH1_COEFFICIENTS } from './harmonics.ts';
|
|
21
|
+
import type { GaussianSet } from './project.ts';
|
|
22
|
+
import type { RefineCloud } from './refine.ts';
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* The step sizes, as fractions of each family's own scale — which is what Adam makes them. They are
|
|
26
|
+
* the reference implementation's, and they are *decisions*: a fit that comes out soft wants the
|
|
27
|
+
* position rate lower and the opacity rate higher, and no test here asserts any of them.
|
|
28
|
+
*
|
|
29
|
+
* **The position rate is the one in real units**, so it is a fraction of the scene rather than a
|
|
30
|
+
* number: the same fit of a room and of a tabletop wants the same *relative* step.
|
|
31
|
+
*/
|
|
32
|
+
const POSITION_RATE = 1.6e-4;
|
|
33
|
+
const SCALE_RATE = 5e-3;
|
|
34
|
+
const ROTATION_RATE = 1e-3;
|
|
35
|
+
const COLOUR_RATE = 2.5e-3;
|
|
36
|
+
/** The band moves at a twentieth of the constant term, as the reference has it. */
|
|
37
|
+
const SH1_RATE = COLOUR_RATE / 20;
|
|
38
|
+
const OPACITY_RATE = 5e-2;
|
|
39
|
+
|
|
40
|
+
/** Where a Gaussian starts: faint enough that the fit adds light rather than removing it. */
|
|
41
|
+
const INITIAL_OPACITY = 0.1;
|
|
42
|
+
/** A Gaussian wider than this fraction of the scene is split rather than cloned. */
|
|
43
|
+
export const SPLIT_FRACTION = 0.01;
|
|
44
|
+
|
|
45
|
+
/** The families, and the scratch the refinement rewrites them through. */
|
|
46
|
+
export interface Cloud extends RefineCloud {
|
|
47
|
+
readonly colors: Family;
|
|
48
|
+
readonly sh1: Family | null;
|
|
49
|
+
readonly logits: Family;
|
|
50
|
+
readonly scales: Float64Array;
|
|
51
|
+
readonly budget: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createCloud(budget: number, extent: number, viewDependent: boolean): Cloud {
|
|
55
|
+
const positions = createFamily(3, POSITION_RATE * extent, budget);
|
|
56
|
+
const logScales = createFamily(3, SCALE_RATE, budget);
|
|
57
|
+
const rotations = createFamily(4, ROTATION_RATE, budget);
|
|
58
|
+
const colors = createFamily(3, COLOUR_RATE, budget);
|
|
59
|
+
const sh1 = viewDependent ? createFamily(SH1_COEFFICIENTS, SH1_RATE, budget) : null;
|
|
60
|
+
const logits = createFamily(1, OPACITY_RATE, budget);
|
|
61
|
+
const families = [positions, logScales, rotations, colors, logits];
|
|
62
|
+
if (sh1 !== null) families.push(sh1);
|
|
63
|
+
return {
|
|
64
|
+
count: 0,
|
|
65
|
+
families,
|
|
66
|
+
positions,
|
|
67
|
+
logScales,
|
|
68
|
+
rotations,
|
|
69
|
+
colors,
|
|
70
|
+
sh1,
|
|
71
|
+
logits,
|
|
72
|
+
scales: new Float64Array(budget * 3),
|
|
73
|
+
opacities: new Float64Array(budget),
|
|
74
|
+
motion: new Float64Array(budget),
|
|
75
|
+
sources: new Int32Array(budget),
|
|
76
|
+
reverse: new Int32Array(budget),
|
|
77
|
+
fresh: new Uint8Array(budget),
|
|
78
|
+
scratch: new Float64Array(budget * SH1_COEFFICIENTS),
|
|
79
|
+
budget,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Every seed as one grey, faint, round Gaussian pointing the way the world does. */
|
|
84
|
+
export function start(cloud: Cloud, seeds: Float64Array, count: number, scale: number): void {
|
|
85
|
+
if (!(scale > 0)) {
|
|
86
|
+
throw new RangeError('capture: a fit needs a starting scale, and a single seed has no spacing');
|
|
87
|
+
}
|
|
88
|
+
const logScale = exactLog(scale);
|
|
89
|
+
const logit = exactLog(INITIAL_OPACITY / (1 - INITIAL_OPACITY));
|
|
90
|
+
for (let at = 0; at < count; at += 1) {
|
|
91
|
+
for (let c = 0; c < 3; c += 1) {
|
|
92
|
+
cloud.positions.values[at * 3 + c] = seeds[at * 3 + c] as number;
|
|
93
|
+
cloud.logScales.values[at * 3 + c] = logScale;
|
|
94
|
+
cloud.colors.values[at * 3 + c] = 0.5;
|
|
95
|
+
}
|
|
96
|
+
cloud.rotations.values[at * 4 + 3] = 1;
|
|
97
|
+
cloud.logits.values[at] = logit;
|
|
98
|
+
}
|
|
99
|
+
cloud.count = count;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The transformed parameters as the rasteriser wants them: real scales and real opacities. */
|
|
103
|
+
export function materialise(cloud: Cloud): void {
|
|
104
|
+
for (let at = 0; at < cloud.count; at += 1) {
|
|
105
|
+
for (let c = 0; c < 3; c += 1) {
|
|
106
|
+
cloud.scales[at * 3 + c] = exactExp(cloud.logScales.values[at * 3 + c] as number);
|
|
107
|
+
}
|
|
108
|
+
cloud.opacities[at] = 1 / (1 + exactExp(-(cloud.logits.values[at] as number)));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** One `GaussianSet` over the cloud's own arrays, whose `count` moves as the cloud does. */
|
|
113
|
+
export function viewOf(cloud: Cloud, budget: number): GaussianSet {
|
|
114
|
+
const set: GaussianSet = {
|
|
115
|
+
count: 0,
|
|
116
|
+
positions: cloud.positions.values,
|
|
117
|
+
scales: cloud.scales,
|
|
118
|
+
rotations: cloud.rotations.values,
|
|
119
|
+
colors: cloud.colors.values,
|
|
120
|
+
opacities: cloud.opacities,
|
|
121
|
+
};
|
|
122
|
+
return cloud.sh1 === null ? set : { ...set, sh1: cloud.sh1.values.subarray(0, budget * 9) };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function clear(gradients: GaussianGradients, count: number): void {
|
|
126
|
+
gradients.positions.fill(0, 0, count * 3);
|
|
127
|
+
gradients.scales.fill(0, 0, count * 3);
|
|
128
|
+
gradients.rotations.fill(0, 0, count * 4);
|
|
129
|
+
gradients.colors.fill(0, 0, count * 3);
|
|
130
|
+
gradients.sh1.fill(0, 0, count * SH1_COEFFICIENTS);
|
|
131
|
+
gradients.screen.fill(0, 0, count * SCREEN_GRADIENTS);
|
|
132
|
+
gradients.opacities.fill(0, 0, count);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The render's gradients onto the parameters that are actually stepped.
|
|
137
|
+
*
|
|
138
|
+
* Position, rotation, colour and the band are themselves; a scale arrives as a derivative by the
|
|
139
|
+
* metre and is wanted by the logarithm, which is one factor of the scale; an opacity arrives by the
|
|
140
|
+
* proportion and is wanted by the logit, which is the logistic's own derivative. **This is also
|
|
141
|
+
* where each Gaussian's pull is recorded** — how hard the frames are asking it to move, which is
|
|
142
|
+
* what the refinement grows the busiest of.
|
|
143
|
+
*/
|
|
144
|
+
export function chain(cloud: Cloud, gradients: GaussianGradients): void {
|
|
145
|
+
for (let at = 0; at < cloud.count; at += 1) {
|
|
146
|
+
let pull = 0;
|
|
147
|
+
for (let c = 0; c < 3; c += 1) {
|
|
148
|
+
const move = gradients.positions[at * 3 + c] as number;
|
|
149
|
+
cloud.positions.gradient[at * 3 + c] = move;
|
|
150
|
+
pull += move * move;
|
|
151
|
+
cloud.logScales.gradient[at * 3 + c] =
|
|
152
|
+
(gradients.scales[at * 3 + c] as number) * (cloud.scales[at * 3 + c] as number);
|
|
153
|
+
cloud.colors.gradient[at * 3 + c] = gradients.colors[at * 3 + c] as number;
|
|
154
|
+
}
|
|
155
|
+
cloud.motion[at] = (cloud.motion[at] as number) + Math.sqrt(pull);
|
|
156
|
+
for (let c = 0; c < 4; c += 1) {
|
|
157
|
+
cloud.rotations.gradient[at * 4 + c] = gradients.rotations[at * 4 + c] as number;
|
|
158
|
+
}
|
|
159
|
+
const opacity = cloud.opacities[at] as number;
|
|
160
|
+
cloud.logits.gradient[at] = (gradients.opacities[at] as number) * opacity * (1 - opacity);
|
|
161
|
+
if (cloud.sh1 === null) continue;
|
|
162
|
+
for (let c = 0; c < SH1_COEFFICIENTS; c += 1) {
|
|
163
|
+
cloud.sh1.gradient[at * SH1_COEFFICIENTS + c] = gradients.sh1[
|
|
164
|
+
at * SH1_COEFFICIENTS + c
|
|
165
|
+
] as number;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The fitted cloud as a capture: single precision, cut to what it holds. */
|
|
171
|
+
export function harvest(cloud: Cloud): SplatSource {
|
|
172
|
+
const { count } = cloud;
|
|
173
|
+
const source: SplatSource = {
|
|
174
|
+
count,
|
|
175
|
+
positions: Float32Array.from(cloud.positions.values.subarray(0, count * 3)),
|
|
176
|
+
scales: Float32Array.from(cloud.scales.subarray(0, count * 3)),
|
|
177
|
+
rotations: Float32Array.from(cloud.rotations.values.subarray(0, count * 4)),
|
|
178
|
+
colors: Float32Array.from(cloud.colors.values.subarray(0, count * 3)),
|
|
179
|
+
opacities: Float32Array.from(cloud.opacities.subarray(0, count)),
|
|
180
|
+
};
|
|
181
|
+
if (cloud.sh1 === null) return source;
|
|
182
|
+
return {
|
|
183
|
+
...source,
|
|
184
|
+
sh1: Float32Array.from(cloud.sh1.values.subarray(0, count * SH1_COEFFICIENTS)),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a Gaussian lands on screen: the projection `@driftengine/splats`' shader performs, in
|
|
3
|
+
* JavaScript, and the order a frame draws them in.
|
|
4
|
+
*
|
|
5
|
+
* **The same model, in the capture's own frame.** A perspective divide is not linear, so a
|
|
6
|
+
* three-dimensional Gaussian does not project to a two-dimensional one; the standard treatment,
|
|
7
|
+
* which that shader uses and this repeats, linearises about the splat's centre — `Σ' = J·R·Σ·Rᵀ·Jᵀ`
|
|
8
|
+
* — and adds a low-pass of 0.3 to the screen variances so that a splat smaller than a pixel still
|
|
9
|
+
* covers one. The one difference is handedness: the shader works in the renderer's view space,
|
|
10
|
+
* where z runs away from the camera negatively, and a capture's cameras look down positive z. The
|
|
11
|
+
* arithmetic is the same and the frame is the caller's.
|
|
12
|
+
*
|
|
13
|
+
* **It exists to be differentiated.** `projectGradient.ts` is this chain run backwards, and
|
|
14
|
+
* `gradients.test.ts` holds the two together by a central difference of one against the other.
|
|
15
|
+
*/
|
|
16
|
+
export interface GaussianSet {
|
|
17
|
+
count: number;
|
|
18
|
+
/** Three per Gaussian, in the capture's world. */
|
|
19
|
+
readonly positions: Float64Array;
|
|
20
|
+
/** Three per Gaussian: a standard deviation along each of its own axes, in metres. */
|
|
21
|
+
readonly scales: Float64Array;
|
|
22
|
+
/** Four per Gaussian, xyzw, as the engine's quaternions are. */
|
|
23
|
+
readonly rotations: Float64Array;
|
|
24
|
+
/** Three per Gaussian, linear, which is a spherical harmonic's constant term. */
|
|
25
|
+
readonly colors: Float64Array;
|
|
26
|
+
/** Nine per Gaussian for the degree-1 band, or absent for a cloud with no view dependence. */
|
|
27
|
+
readonly sh1?: Float64Array;
|
|
28
|
+
/** One per Gaussian, 0 to 1. */
|
|
29
|
+
readonly opacities: Float64Array;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A frame this rasteriser writes: **single precision where it stands in for a render target, double
|
|
34
|
+
* where the caller is differentiating through it.** The fitting loop accumulates its loss in double
|
|
35
|
+
* and a frame quantised to single is a floor under every difference taken through one — measured at
|
|
36
|
+
* about a part in five hundred of the slope, against a part in ten million when the frame is double.
|
|
37
|
+
*/
|
|
38
|
+
export type Frame = Float32Array | Float64Array;
|
|
39
|
+
|
|
40
|
+
export interface RasterCamera {
|
|
41
|
+
readonly width: number;
|
|
42
|
+
readonly height: number;
|
|
43
|
+
/** `fx`, `fy`, `cx`, `cy`. */
|
|
44
|
+
readonly intrinsics: readonly [number, number, number, number];
|
|
45
|
+
/** 3 × 4 row-major world-to-camera, as `estimatePoses` answers. */
|
|
46
|
+
readonly worldToCamera: Float64Array;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The low-pass the shader adds to the screen variances, in square pixels. */
|
|
50
|
+
export const LOW_PASS = 0.3;
|
|
51
|
+
/** How many standard deviations of a splat are drawn, and the largest radius in pixels. */
|
|
52
|
+
export const REACH = 3;
|
|
53
|
+
const MAX_RADIUS = 256;
|
|
54
|
+
|
|
55
|
+
/** One Gaussian as the screen sees it. */
|
|
56
|
+
export interface Projected {
|
|
57
|
+
/** Where its centre lands, in pixels. */
|
|
58
|
+
readonly x: number;
|
|
59
|
+
readonly y: number;
|
|
60
|
+
/** Its screen covariance, `[a, b, d]` for `[[a, b], [b, d]]`, the low-pass included. */
|
|
61
|
+
readonly a: number;
|
|
62
|
+
readonly b: number;
|
|
63
|
+
readonly d: number;
|
|
64
|
+
/** Along the camera's own axis, which is what the painter's order is by. */
|
|
65
|
+
readonly depth: number;
|
|
66
|
+
/** The half-width of its axis-aligned box, in pixels. */
|
|
67
|
+
readonly radius: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The rotation a quaternion stands for, into `out` (3 × 3 row-major). */
|
|
71
|
+
export function rotationFromQuaternion(q: ArrayLike<number>, at: number, out: Float64Array): void {
|
|
72
|
+
const x = q[at * 4] as number;
|
|
73
|
+
const y = q[at * 4 + 1] as number;
|
|
74
|
+
const z = q[at * 4 + 2] as number;
|
|
75
|
+
const w = q[at * 4 + 3] as number;
|
|
76
|
+
const length = Math.sqrt(x * x + y * y + z * z + w * w) || 1;
|
|
77
|
+
const nx = x / length;
|
|
78
|
+
const ny = y / length;
|
|
79
|
+
const nz = z / length;
|
|
80
|
+
const nw = w / length;
|
|
81
|
+
out[0] = 1 - 2 * (ny * ny + nz * nz);
|
|
82
|
+
out[1] = 2 * (nx * ny - nz * nw);
|
|
83
|
+
out[2] = 2 * (nx * nz + ny * nw);
|
|
84
|
+
out[3] = 2 * (nx * ny + nz * nw);
|
|
85
|
+
out[4] = 1 - 2 * (nx * nx + nz * nz);
|
|
86
|
+
out[5] = 2 * (ny * nz - nx * nw);
|
|
87
|
+
out[6] = 2 * (nx * nz - ny * nw);
|
|
88
|
+
out[7] = 2 * (ny * nz + nx * nw);
|
|
89
|
+
out[8] = 1 - 2 * (nx * nx + ny * ny);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** One Gaussian projected, or null where it is behind the camera or too flat to draw. */
|
|
93
|
+
export function projectGaussian(
|
|
94
|
+
set: GaussianSet,
|
|
95
|
+
at: number,
|
|
96
|
+
camera: RasterCamera,
|
|
97
|
+
): Projected | null {
|
|
98
|
+
const [fx, fy, cx, cy] = camera.intrinsics;
|
|
99
|
+
const m = camera.worldToCamera;
|
|
100
|
+
const view = [0, 0, 0];
|
|
101
|
+
for (let r = 0; r < 3; r += 1) {
|
|
102
|
+
view[r] =
|
|
103
|
+
(m[r * 4] as number) * (set.positions[at * 3] as number) +
|
|
104
|
+
(m[r * 4 + 1] as number) * (set.positions[at * 3 + 1] as number) +
|
|
105
|
+
(m[r * 4 + 2] as number) * (set.positions[at * 3 + 2] as number) +
|
|
106
|
+
(m[r * 4 + 3] as number);
|
|
107
|
+
}
|
|
108
|
+
const z = view[2] as number;
|
|
109
|
+
if (!(z > 1e-3)) return null;
|
|
110
|
+
|
|
111
|
+
/* The world covariance: R · diag(s²) · Rᵀ, turned into the camera's frame by the view rotation. */
|
|
112
|
+
const rotation = new Float64Array(9);
|
|
113
|
+
rotationFromQuaternion(set.rotations, at, rotation);
|
|
114
|
+
const scaled = new Float64Array(9);
|
|
115
|
+
for (let r = 0; r < 3; r += 1) {
|
|
116
|
+
for (let c = 0; c < 3; c += 1) {
|
|
117
|
+
const s = set.scales[at * 3 + c] as number;
|
|
118
|
+
scaled[r * 3 + c] = (rotation[r * 3 + c] as number) * s;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const world = new Float64Array(9);
|
|
122
|
+
for (let r = 0; r < 3; r += 1) {
|
|
123
|
+
for (let c = 0; c < 3; c += 1) {
|
|
124
|
+
let sum = 0;
|
|
125
|
+
for (let k = 0; k < 3; k += 1)
|
|
126
|
+
sum += (scaled[r * 3 + k] as number) * (scaled[c * 3 + k] as number);
|
|
127
|
+
world[r * 3 + c] = sum;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const inCamera = new Float64Array(9);
|
|
131
|
+
for (let r = 0; r < 3; r += 1) {
|
|
132
|
+
for (let c = 0; c < 3; c += 1) {
|
|
133
|
+
let sum = 0;
|
|
134
|
+
for (let k = 0; k < 3; k += 1) {
|
|
135
|
+
for (let j = 0; j < 3; j += 1) {
|
|
136
|
+
sum += (m[r * 4 + k] as number) * (world[k * 3 + j] as number) * (m[c * 4 + j] as number);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
inCamera[r * 3 + c] = sum;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* The projection's Jacobian at this point, in pixels. */
|
|
144
|
+
const invZ = 1 / z;
|
|
145
|
+
const invZ2 = invZ * invZ;
|
|
146
|
+
const j = [
|
|
147
|
+
[fx * invZ, 0, -fx * (view[0] as number) * invZ2],
|
|
148
|
+
[0, fy * invZ, -fy * (view[1] as number) * invZ2],
|
|
149
|
+
];
|
|
150
|
+
const screen = [0, 0, 0, 0];
|
|
151
|
+
for (let r = 0; r < 2; r += 1) {
|
|
152
|
+
for (let c = 0; c < 2; c += 1) {
|
|
153
|
+
let sum = 0;
|
|
154
|
+
for (let k = 0; k < 3; k += 1) {
|
|
155
|
+
for (let l = 0; l < 3; l += 1) {
|
|
156
|
+
sum +=
|
|
157
|
+
((j[r] as number[])[k] as number) *
|
|
158
|
+
(inCamera[k * 3 + l] as number) *
|
|
159
|
+
((j[c] as number[])[l] as number);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
screen[r * 2 + c] = sum;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const a = (screen[0] as number) + LOW_PASS;
|
|
166
|
+
const b = screen[1] as number;
|
|
167
|
+
const d = (screen[3] as number) + LOW_PASS;
|
|
168
|
+
const determinant = a * d - b * b;
|
|
169
|
+
if (!(determinant > 1e-12)) return null;
|
|
170
|
+
/* The ellipse's larger eigenvalue fixes how far it reaches. */
|
|
171
|
+
const mid = 0.5 * (a + d);
|
|
172
|
+
const spread = Math.sqrt(Math.max(0.1, mid * mid - determinant));
|
|
173
|
+
const radius = Math.min(MAX_RADIUS, REACH * Math.sqrt(Math.max(mid + spread, 0)));
|
|
174
|
+
return {
|
|
175
|
+
x: fx * (view[0] as number) * invZ + cx,
|
|
176
|
+
y: fy * (view[1] as number) * invZ + cy,
|
|
177
|
+
a,
|
|
178
|
+
b,
|
|
179
|
+
d,
|
|
180
|
+
depth: z,
|
|
181
|
+
radius,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** The Gaussians a camera sees, nearest first, and where each lands. */
|
|
186
|
+
export function visibleGaussians(
|
|
187
|
+
set: GaussianSet,
|
|
188
|
+
camera: RasterCamera,
|
|
189
|
+
): { readonly order: number[]; readonly projected: (Projected | null)[] } {
|
|
190
|
+
const projected: (Projected | null)[] = [];
|
|
191
|
+
const order: number[] = [];
|
|
192
|
+
for (let at = 0; at < set.count; at += 1) {
|
|
193
|
+
const one = projectGaussian(set, at, camera);
|
|
194
|
+
projected.push(one);
|
|
195
|
+
if (one === null) continue;
|
|
196
|
+
if (one.x + one.radius < 0 || one.x - one.radius >= camera.width) continue;
|
|
197
|
+
if (one.y + one.radius < 0 || one.y - one.radius >= camera.height) continue;
|
|
198
|
+
order.push(at);
|
|
199
|
+
}
|
|
200
|
+
/* Nearest first, ties by index so the answer does not depend on the sort's stability. */
|
|
201
|
+
order.sort((p, q) => {
|
|
202
|
+
const first = (projected[p] as Projected).depth;
|
|
203
|
+
const second = (projected[q] as Projected).depth;
|
|
204
|
+
return first === second ? p - q : first - second;
|
|
205
|
+
});
|
|
206
|
+
return { order, projected };
|
|
207
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
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, type GaussianSet, type RasterCamera } from './project.ts';
|
|
10
|
+
|
|
11
|
+
import type { GaussianGradients } from './gradients.ts';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The screen-space gradients carried back to a Gaussian's position, scale and rotation, by the
|
|
15
|
+
* same chain the projection is built from.
|
|
16
|
+
*/
|
|
17
|
+
export function screenToParameters(
|
|
18
|
+
set: GaussianSet,
|
|
19
|
+
at: number,
|
|
20
|
+
camera: RasterCamera,
|
|
21
|
+
dMeanX: number,
|
|
22
|
+
dMeanY: number,
|
|
23
|
+
dA: number,
|
|
24
|
+
dB: number,
|
|
25
|
+
dD: number,
|
|
26
|
+
out: GaussianGradients,
|
|
27
|
+
): void {
|
|
28
|
+
const [fx, fy] = camera.intrinsics;
|
|
29
|
+
const m = camera.worldToCamera;
|
|
30
|
+
const view = [0, 0, 0];
|
|
31
|
+
for (let r = 0; r < 3; r += 1) {
|
|
32
|
+
view[r] =
|
|
33
|
+
(m[r * 4] as number) * (set.positions[at * 3] as number) +
|
|
34
|
+
(m[r * 4 + 1] as number) * (set.positions[at * 3 + 1] as number) +
|
|
35
|
+
(m[r * 4 + 2] as number) * (set.positions[at * 3 + 2] as number) +
|
|
36
|
+
(m[r * 4 + 3] as number);
|
|
37
|
+
}
|
|
38
|
+
const z = view[2] as number;
|
|
39
|
+
const invZ = 1 / z;
|
|
40
|
+
const invZ2 = invZ * invZ;
|
|
41
|
+
const invZ3 = invZ2 * invZ;
|
|
42
|
+
|
|
43
|
+
/* The world covariance in the camera's frame, which the Jacobian acts on. */
|
|
44
|
+
const rotation = new Float64Array(9);
|
|
45
|
+
rotationFromQuaternion(set.rotations, at, rotation);
|
|
46
|
+
const scaled = new Float64Array(9);
|
|
47
|
+
for (let r = 0; r < 3; r += 1) {
|
|
48
|
+
for (let c = 0; c < 3; c += 1) {
|
|
49
|
+
scaled[r * 3 + c] = (rotation[r * 3 + c] as number) * (set.scales[at * 3 + c] as number);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const world = 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
|
+
sum += (scaled[r * 3 + k] as number) * (scaled[c * 3 + k] as number);
|
|
58
|
+
world[r * 3 + c] = sum;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const viewRotation = new Float64Array(9);
|
|
62
|
+
for (let r = 0; r < 3; r += 1) {
|
|
63
|
+
for (let c = 0; c < 3; c += 1) viewRotation[r * 3 + c] = m[r * 4 + c] as number;
|
|
64
|
+
}
|
|
65
|
+
const inCamera = new Float64Array(9);
|
|
66
|
+
for (let r = 0; r < 3; r += 1) {
|
|
67
|
+
for (let c = 0; c < 3; c += 1) {
|
|
68
|
+
let sum = 0;
|
|
69
|
+
for (let k = 0; k < 3; k += 1) {
|
|
70
|
+
for (let j = 0; j < 3; j += 1) {
|
|
71
|
+
sum +=
|
|
72
|
+
(viewRotation[r * 3 + k] as number) *
|
|
73
|
+
(world[k * 3 + j] as number) *
|
|
74
|
+
(viewRotation[c * 3 + j] as number);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
inCamera[r * 3 + c] = sum;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const j = [
|
|
82
|
+
[fx * invZ, 0, -fx * (view[0] as number) * invZ2],
|
|
83
|
+
[0, fy * invZ, -fy * (view[1] as number) * invZ2],
|
|
84
|
+
];
|
|
85
|
+
/* dΣ'/dΣcam, then the same into the world and the Gaussian's own axes. */
|
|
86
|
+
const dScreen = [
|
|
87
|
+
[dA, dB / 2],
|
|
88
|
+
[dB / 2, dD],
|
|
89
|
+
];
|
|
90
|
+
const dCamera = new Float64Array(9);
|
|
91
|
+
for (let k = 0; k < 3; k += 1) {
|
|
92
|
+
for (let l = 0; l < 3; l += 1) {
|
|
93
|
+
let sum = 0;
|
|
94
|
+
for (let r = 0; r < 2; r += 1) {
|
|
95
|
+
for (let c = 0; c < 2; c += 1) {
|
|
96
|
+
sum +=
|
|
97
|
+
((dScreen[r] as number[])[c] as number) *
|
|
98
|
+
((j[r] as number[])[k] as number) *
|
|
99
|
+
((j[c] as number[])[l] as number);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
dCamera[k * 3 + l] = sum;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const dWorld = new Float64Array(9);
|
|
106
|
+
for (let k = 0; k < 3; k += 1) {
|
|
107
|
+
for (let l = 0; l < 3; l += 1) {
|
|
108
|
+
let sum = 0;
|
|
109
|
+
for (let r = 0; r < 3; r += 1) {
|
|
110
|
+
for (let c = 0; c < 3; c += 1) {
|
|
111
|
+
sum +=
|
|
112
|
+
(viewRotation[r * 3 + k] as number) *
|
|
113
|
+
(dCamera[r * 3 + c] as number) *
|
|
114
|
+
(viewRotation[c * 3 + l] as number);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
dWorld[k * 3 + l] = sum;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Σ = M · Mᵀ with M = R · diag(s), so dΣ/dM = 2 · dΣ · M for a symmetric dΣ. */
|
|
122
|
+
const dM = new Float64Array(9);
|
|
123
|
+
for (let r = 0; r < 3; r += 1) {
|
|
124
|
+
for (let c = 0; c < 3; c += 1) {
|
|
125
|
+
let sum = 0;
|
|
126
|
+
for (let k = 0; k < 3; k += 1)
|
|
127
|
+
sum += (dWorld[r * 3 + k] as number) * (scaled[k * 3 + c] as number);
|
|
128
|
+
dM[r * 3 + c] = 2 * sum;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (let c = 0; c < 3; c += 1) {
|
|
132
|
+
let sum = 0;
|
|
133
|
+
for (let r = 0; r < 3; r += 1)
|
|
134
|
+
sum += (dM[r * 3 + c] as number) * (rotation[r * 3 + c] as number);
|
|
135
|
+
out.scales[at * 3 + c] = (out.scales[at * 3 + c] as number) + sum;
|
|
136
|
+
}
|
|
137
|
+
/* And through the rotation to the quaternion, by its own derivatives. */
|
|
138
|
+
const dRotation = new Float64Array(9);
|
|
139
|
+
for (let r = 0; r < 3; r += 1) {
|
|
140
|
+
for (let c = 0; c < 3; c += 1) {
|
|
141
|
+
dRotation[r * 3 + c] = (dM[r * 3 + c] as number) * (set.scales[at * 3 + c] as number);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
quaternionGradient(set.rotations, at, dRotation, out.rotations);
|
|
145
|
+
|
|
146
|
+
/*
|
|
147
|
+
* The position moves the mean and, through the Jacobian, the covariance. The second term is what
|
|
148
|
+
* a gradient written from the mean alone misses — it is small at the centre of the frame and is
|
|
149
|
+
* not small at its edges.
|
|
150
|
+
*/
|
|
151
|
+
const dView = [
|
|
152
|
+
dMeanX * fx * invZ,
|
|
153
|
+
dMeanY * fy * invZ,
|
|
154
|
+
-dMeanX * fx * (view[0] as number) * invZ2 - dMeanY * fy * (view[1] as number) * invZ2,
|
|
155
|
+
];
|
|
156
|
+
/* dJ/dview, the only part of Σ' that the position enters. */
|
|
157
|
+
const dJ = [
|
|
158
|
+
[
|
|
159
|
+
[0, 0, -fx * invZ2],
|
|
160
|
+
[0, 0, 0],
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
[0, 0, 0],
|
|
164
|
+
[0, 0, -fy * invZ2],
|
|
165
|
+
],
|
|
166
|
+
[
|
|
167
|
+
[-fx * invZ2, 0, 2 * fx * (view[0] as number) * invZ3],
|
|
168
|
+
[0, -fy * invZ2, 2 * fy * (view[1] as number) * invZ3],
|
|
169
|
+
],
|
|
170
|
+
];
|
|
171
|
+
for (let k = 0; k < 3; k += 1) {
|
|
172
|
+
for (let l = 0; l < 3; l += 1) {
|
|
173
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
174
|
+
let sum = 0;
|
|
175
|
+
for (let r = 0; r < 2; r += 1) {
|
|
176
|
+
for (let c = 0; c < 2; c += 1) {
|
|
177
|
+
const term =
|
|
178
|
+
(((dJ[axis] as number[][])[r] as number[])[k] as number) *
|
|
179
|
+
((j[c] as number[])[l] as number) +
|
|
180
|
+
((j[r] as number[])[k] as number) *
|
|
181
|
+
(((dJ[axis] as number[][])[c] as number[])[l] as number);
|
|
182
|
+
sum += ((dScreen[r] as number[])[c] as number) * term * (inCamera[k * 3 + l] as number);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
dView[axis] = (dView[axis] as number) + sum;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
190
|
+
let sum = 0;
|
|
191
|
+
for (let r = 0; r < 3; r += 1) sum += (dView[r] as number) * (m[r * 4 + axis] as number);
|
|
192
|
+
out.positions[at * 3 + axis] = (out.positions[at * 3 + axis] as number) + sum;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** A rotation matrix's gradient carried back to the quaternion it came from. */
|
|
197
|
+
function quaternionGradient(
|
|
198
|
+
q: Float64Array,
|
|
199
|
+
at: number,
|
|
200
|
+
dRotation: Float64Array,
|
|
201
|
+
out: Float64Array,
|
|
202
|
+
): void {
|
|
203
|
+
const x = q[at * 4] as number;
|
|
204
|
+
const y = q[at * 4 + 1] as number;
|
|
205
|
+
const z = q[at * 4 + 2] as number;
|
|
206
|
+
const w = q[at * 4 + 3] as number;
|
|
207
|
+
const length = Math.sqrt(x * x + y * y + z * z + w * w) || 1;
|
|
208
|
+
const nx = x / length;
|
|
209
|
+
const ny = y / length;
|
|
210
|
+
const nz = z / length;
|
|
211
|
+
const nw = w / length;
|
|
212
|
+
/* dR/d(normalised quaternion), the nine entries against each of the four. */
|
|
213
|
+
const d = [
|
|
214
|
+
[0, 2 * ny, 2 * nz, 2 * ny, -4 * nx, -2 * nw, 2 * nz, 2 * nw, -4 * nx],
|
|
215
|
+
[-4 * ny, 2 * nx, 2 * nw, 2 * nx, 0, 2 * nz, -2 * nw, 2 * nz, -4 * ny],
|
|
216
|
+
[-4 * nz, -2 * nw, 2 * nx, 2 * nw, -4 * nz, 2 * ny, 2 * nx, 2 * ny, 0],
|
|
217
|
+
[0, -2 * nz, 2 * ny, 2 * nz, 0, -2 * nx, -2 * ny, 2 * nx, 0],
|
|
218
|
+
];
|
|
219
|
+
/* Through the normalisation: the part of a change that lengthens the quaternion does nothing. */
|
|
220
|
+
const raw = [0, 0, 0, 0];
|
|
221
|
+
for (let p = 0; p < 4; p += 1) {
|
|
222
|
+
let sum = 0;
|
|
223
|
+
for (let i = 0; i < 9; i += 1)
|
|
224
|
+
sum += ((d[p] as number[])[i] as number) * (dRotation[i] as number);
|
|
225
|
+
raw[p] = sum;
|
|
226
|
+
}
|
|
227
|
+
const unit = [nx, ny, nz, nw];
|
|
228
|
+
let along = 0;
|
|
229
|
+
for (let p = 0; p < 4; p += 1) along += (raw[p] as number) * (unit[p] as number);
|
|
230
|
+
for (let p = 0; p < 4; p += 1) {
|
|
231
|
+
out[at * 4 + p] =
|
|
232
|
+
(out[at * 4 + p] as number) + ((raw[p] as number) - along * (unit[p] as number)) / length;
|
|
233
|
+
}
|
|
234
|
+
}
|